A clear explanation of merging two quad-trees using recursive logical OR operations.
Problem Restatement
We are given two quad-trees representing two binary grids.
Each grid cell contains either:
| Value | Meaning |
|---|---|
0 | False |
1 | True |
We need to compute the logical OR of the two grids and return the result as a quad-tree.
The logical OR rule is:
| A | B | A OR B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
Each quad-tree node contains:
| Field | Meaning |
|---|---|
val | Boolean value |
isLeaf | Whether the node is a leaf |
topLeft | Top-left child |
topRight | Top-right child |
bottomLeft | Bottom-left child |
bottomRight | Bottom-right child |
A leaf node means the entire represented region has one uniform value.
The problem asks us to return a quad-tree representing the OR result. The statement guarantees both input trees represent grids of the same size. (github.com)
Input and Output
| Item | Meaning |
|---|---|
| Input | Two quad-tree roots |
| Output | Root of the OR result quad-tree |
| Operation | Logical OR |
| Grid sizes | Same size |
Example function shape:
def intersect(quadTree1: 'Node', quadTree2: 'Node') -> 'Node':
...Understanding Quad-Trees
A quad-tree recursively divides a square into four equal regions:
| Child | Region |
|---|---|
topLeft | Upper-left |
topRight | Upper-right |
bottomLeft | Lower-left |
bottomRight | Lower-right |
A leaf node represents a completely uniform region.
For example:
isLeaf = True
val = 1means the entire represented square is filled with 1.
First Thought: Rebuild the Entire Grid
One idea is:
- Expand both quad-trees into full binary grids.
- Compute the OR cell by cell.
- Compress the result back into a quad-tree.
This works logically, but it wastes memory and ignores the compression already provided by quad-trees.
We should operate directly on the trees.
Key Insight
Quad-trees already represent large uniform regions compactly.
Suppose one node is:
isLeaf = True
val = 1Then the entire region is all 1.
For logical OR:
1 OR anything = 1So we can immediately return that node without exploring the other tree.
Similarly:
0 OR x = xSo if a leaf node has value 0, the result is exactly the other subtree.
This gives a recursive solution.
Important Cases
Case 1: First Node Is Leaf With Value 1
1 OR x = 1Return the first node.
Case 2: Second Node Is Leaf With Value 1
x OR 1 = 1Return the second node.
Case 3: First Node Is Leaf With Value 0
0 OR x = xReturn the second node.
Case 4: Second Node Is Leaf With Value 0
x OR 0 = xReturn the first node.
Case 5: Both Nodes Are Internal
Recursively OR the four children.
Algorithm
For nodes a and b:
If
ais a leaf:- If
a.val == True, returna. - Otherwise return
b.
- If
If
bis a leaf:- If
b.val == True, returnb. - Otherwise return
a.
- If
Recursively compute:
topLeft topRight bottomLeft bottomRightAfter recursion, check whether all four children are leaves with the same value.
If yes, compress them into one leaf node.
Otherwise return an internal node containing those four children.
Why Compression Is Needed
Suppose after recursion we get:
| Child | Value |
|---|---|
| topLeft | leaf 1 |
| topRight | leaf 1 |
| bottomLeft | leaf 1 |
| bottomRight | leaf 1 |
These four children together represent one uniform region.
Instead of keeping four separate nodes, we compress them into:
isLeaf = True
val = 1This keeps the tree minimal.
Correctness
The algorithm follows the exact definition of logical OR.
If one node is a leaf with value 1, then every cell in that represented region is 1. Since:
1 OR x = 1the entire output region must also be all 1. Returning that leaf is correct.
If one node is a leaf with value 0, then:
0 OR x = xSo the output region is exactly the other subtree. Returning the other subtree is correct.
When both nodes are internal, the represented regions are subdivided into four matching quadrants. The logical OR for the whole region is therefore obtained by computing the OR independently on each corresponding quadrant. The recursive calls correctly compute these four quadrant results.
After recursion, if all four children are leaves with the same value, then the whole region is uniform. Replacing those four leaves with one larger leaf preserves the represented grid exactly while producing a more compact quad-tree.
Therefore, every returned node represents exactly the logical OR of the corresponding regions in the two input trees.
Complexity
Let n be the total number of nodes visited.
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | Each relevant node pair is processed once |
| Space | O(h) | Recursive call stack height |
Here h is the maximum tree height.
In the worst case, all nodes may need to be explored.
Implementation
class Solution:
def intersect(self, quadTree1: 'Node', quadTree2: 'Node') -> 'Node':
if quadTree1.isLeaf:
if quadTree1.val:
return quadTree1
return quadTree2
if quadTree2.isLeaf:
if quadTree2.val:
return quadTree2
return quadTree1
topLeft = self.intersect(quadTree1.topLeft, quadTree2.topLeft)
topRight = self.intersect(quadTree1.topRight, quadTree2.topRight)
bottomLeft = self.intersect(quadTree1.bottomLeft, quadTree2.bottomLeft)
bottomRight = self.intersect(quadTree1.bottomRight, quadTree2.bottomRight)
children = [topLeft, topRight, bottomLeft, bottomRight]
if (
all(child.isLeaf for child in children)
and topLeft.val == topRight.val == bottomLeft.val == bottomRight.val
):
return Node(topLeft.val, True)
return Node(
False,
False,
topLeft,
topRight,
bottomLeft,
bottomRight,
)Code Explanation
We first handle leaf shortcuts.
If the first tree is a leaf:
if quadTree1.isLeaf:and its value is True, then the OR result must also be True everywhere in that region:
if quadTree1.val:
return quadTree1Otherwise, the first region is all 0, so the result is just the second tree:
return quadTree2We do the symmetric logic for the second tree.
If both nodes are internal, we recursively combine corresponding children:
topLeft = self.intersect(...)After recursion, we check whether all children became identical leaf nodes:
all(child.isLeaf for child in children)and:
topLeft.val == topRight.val == bottomLeft.val == bottomRight.valIf so, we compress them into one leaf node.
Otherwise, we return an internal node containing the four children.
Testing
def run_tests():
s = Solution()
# Leaf true OR leaf false -> true
a = Node(True, True)
b = Node(False, True)
result = s.intersect(a, b)
assert result.isLeaf is True
assert result.val is True
# Leaf false OR leaf false -> false
a = Node(False, True)
b = Node(False, True)
result = s.intersect(a, b)
assert result.isLeaf is True
assert result.val is False
# Leaf false OR internal -> internal
internal = Node(
False,
False,
Node(True, True),
Node(False, True),
Node(False, True),
Node(True, True),
)
result = s.intersect(Node(False, True), internal)
assert result.isLeaf is False
print("all tests passed")| Test | Why |
|---|---|
1 OR 0 | Immediate leaf shortcut |
0 OR 0 | Uniform false region |
0 OR internal | Internal subtree should remain unchanged |
| Recursive internal cases | Checks recursive merging logic |
| Compression cases | Ensures identical children collapse into one leaf |