Example | t1 | t2 | Answer |
0 | "(())" | "()" | "Possible" |
1 | "()" | "()" | "Possible" |
2 | "(()()())" | "((()))" | "Impossible" |
3 | "((())((())())())" | "(()(())())" | "Possible" |
4 | "((())((())())())" | "((()()()()()))" | "Impossible" |
5 (I added this one) | "(()((()()))(()(()))((()()))()(()(()())(()))())" | "((())((()()))(()))" | "Possible" |
Let's start off by looking at the examples. When it's "Possible", I show the nodes that you delete in blue. Make sure you see how examples 3 and 5 return "Possible". I've labeled some subtrees in those examples to help with the explanation.
Example 0: Possible |
t1 | t2 |
Example 1: Possible |
t1 | t2 |
Example 2: Impossible |
t1 | t2 |
Example 3: Possible |
t1 | t2 |
Example 4: Impossible |
t1 | t2 |
Example 5: Possible |
t1 | t2 |
Observation #1: While the problem description talks ./a.out deleting leaf nodes, in reality, you are deleting subtrees of T1 to match T2. Look again at example 5 -- there you should be able to see that repeatedly deleting leaf nodes is equivalent to deleting subtrees. I've labeled the subtrees in the first level of example 5, so that it is easier to talk ./a.out that example.
Observation #2: When you are matching t1 and t2, you cannot delete the root nodes. This is because you delete subtrees of t1. If you delete the root of t1, then you will have deleted the entire tree.
Observation #3: If t1 is a single node, and t2 is more than a single node, then t1 will not match t2. That is the case if you try to match a1 and b1 in example 5 above.
Observation #4: If t2 is a single node, then t1 will match t2. That's because you can delete all of t1 except for the root, and then it matches t2. That's what happens in example 0, and with subtrees c1 and d1 in example 3.
Observation #5: Let us suppose now that t1 and t2 are not single nodes. Each of them is the root of an ordered collection of subtrees. Let's call t1's subtrees x1, x2, ... and t2's subtrees y1, y2, ... If x1 matches y1, then you can delete both subtrees, and solve the problem on the remaining subtrees. You can see this best in example 3:
class Node { public: int left_paren, right_paren; vector <class Node *> children; void Print(); }; |
And then I wrote a procedure to turn strings into trees. I represent a tree with a single pointer to the tree's root node:
Node *StringToTree(const string &s, int lp); |
The parameters are as follows:
a = StringToTree("()", 0) | |
b = StringToTree("(())", 1) | |
c = StringToTree("(())", 0) |
UNIX> ./a.out 0 Calling StringToTree on t1 STT("(())", 0) called. STT("(())", 1) called. STT("(())", 1) returning: left_paren=1, right_paren=2, children.size()=0 STT("(())", 0) returning: left_paren=0, right_paren=3, children.size()=1 Calling StringToTree on t2 STT("()", 0) called. STT("()", 0) returning: left_paren=0, right_paren=1, children.size()=0 UNIX>
UNIX> ./a.out 2 Calling StringToTree on t1 STT("(()()())", 0) called. STT("(()()())", 1) called. STT("(()()())", 1) returning: left_paren=1, right_paren=2, children.size()=0 STT("(()()())", 3) called. STT("(()()())", 3) returning: left_paren=3, right_paren=4, children.size()=0 STT("(()()())", 5) called. STT("(()()())", 5) returning: left_paren=5, right_paren=6, children.size()=0 STT("(()()())", 0) returning: left_paren=0, right_paren=7, children.size()=3 Calling StringToTree on t2 STT("((()))", 0) called. STT("((()))", 1) called. STT("((()))", 2) called. STT("((()))", 2) returning: left_paren=2, right_paren=3, children.size()=0 STT("((()))", 1) returning: left_paren=1, right_paren=4, children.size()=1 STT("((()))", 0) returning: left_paren=0, right_paren=5, children.size()=1 UNIX>To help you out a little, here are examples 0, 2 and 3 with each node labeled by its left_paren value:
Example 0: Possible |
t1 | t2 |
Example 2: Impossible |
t1 | t2 |
Example 3: Possible |
t1 | t2 |
And here is the program on example 2 -- the picture above should help you verify the output:
UNIX> ./a.out 2 Calling StringToTree on t1 STT("(()()())", 0) called. STT("(()()())", 1) called. STT("(()()())", 1) returning: left_paren=1, right_paren=2, children.size()=0 STT("(()()())", 3) called. STT("(()()())", 3) returning: left_paren=3, right_paren=4, children.size()=0 STT("(()()())", 5) called. STT("(()()())", 5) returning: left_paren=5, right_paren=6, children.size()=0 STT("(()()())", 0) returning: left_paren=0, right_paren=7, children.size()=3 Calling StringToTree on t2 STT("((()))", 0) called. STT("((()))", 1) called. STT("((()))", 2) called. STT("((()))", 2) returning: left_paren=2, right_paren=3, children.size()=0 STT("((()))", 1) returning: left_paren=1, right_paren=4, children.size()=1 STT("((()))", 0) returning: left_paren=0, right_paren=5, children.size()=1 UNIX>
string TreesAndBrackets::check(string t1, string t2) { Node *n1, *n2; n1 = StringToTree(t1, 0); n2 = StringToTree(t2, 0); printf("LP RP - Children\n"); n1->Print(); printf("\n"); printf("LP RP - Children\n"); n2->Print(); printf("\n"); |
Here's the output on examples 0 and 2:
UNIX> ./a.out 0 LP RP - Children 0 3 - 1 1 2 - LP RP - Children 0 1 - UNIX> ./a.out 2 LP RP - Children 0 7 - 1 3 5 1 2 - 3 4 - 5 6 - LP RP - Children 0 5 - 1 1 4 - 2 2 3 - UNIX>
bool Match(const Node *n1, const Node *n2); |
This returns true if the subtree rooted at n1 matches the subtree rooted at n2. Again, it will be recursive. Mine went through the following steps.
UNIX> ./a.out 0 Match(0,0) called. Match(0,0) returns true. Possible UNIX> ./a.out 2 Match(0,0) called. Match(1,1) called. Match(1,1) returns false. Match(3,1) called. Match(3,1) returns false. Match(5,1) called. Match(5,1) returns false. Match(0,0) returns false. Impossible UNIX> ./a.out 3 Match(0,0) called. Match(1,1) called. Match(1,1) returns true. Match(5,3) called. Match(6,4) called. Match(6,4) returns true. Match(5,3) returns true. Match(13,7) called. Match(13,7) returns true. Match(0,0) returns true. Possible UNIX>When you're done, delete the extra print statements, and run tests.sh to see if you match answers.txt.