Logical OR of Two Binary Grids Represented as Quad-Trees
MedPrompt
A Binary Matrix is a matrix in which all the elements are either 0 or 1.
Given quadTree1 and quadTree2. quadTree1 represents a n * n binary matrix and quadTree2 represents another n * n binary matrix.
Return a Quad-Tree representing the n * n binary matrix which is the result of logical bitwise OR of the two binary matrixes represented by quadTree1 and quadTree2.
Notice that you can assign the value of a node to True or False when isLeaf is False, and both are accepted in the answer.
A Quad-Tree is a tree data structure in which each internal node has exactly four children. Besides, each node has two attributes:
val: True if the node represents a grid of 1's or False if the node represents a grid of 0's.isLeaf: True if the node is leaf node on the tree or False if the node has the four children.
class Node {
public boolean val;
public boolean isLeaf;
public Node topLeft;
public Node topRight;
public Node bottomLeft;
public Node bottomRight;
}We can construct a Quad-Tree from a two-dimensional area using the following steps:
- If the current grid has the same value (i.e all
1'sor all0's) setisLeafTrue and setvalto the value of the grid and set the four children to Null and stop. - If the current grid has different values, set
isLeafto False and setvalto any value and divide the current grid into four sub-grids as shown in the photo. - Recurse for each of the children with the proper sub-grid.
If you want to know more about the Quad-Tree, you can refer to the wiki.
Quad-Tree format:
The input/output represents the serialized format of a Quad-Tree using level order traversal, where null signifies a path terminator where no node exists below.
It is very similar to the serialization of the binary tree. The only difference is that the node is represented as a list [isLeaf, val].
If the value of isLeaf or val is True we represent it as 1 in the list [isLeaf, val] and if the value of isLeaf or val is False we represent it as 0.
Example 1:
Input: quadTree1 = [[0,1],[1,1],[1,1],[1,0],[1,0]]
, quadTree2 = [[0,1],[1,1],[0,1],[1,1],[1,0],null,null,null,null,[1,0],[1,0],[1,1],[1,1]]
Output: [[0,0],[1,1],[1,1],[1,1],[1,0]]
Explanation: quadTree1 and quadTree2 are shown above. You can see the binary matrix which is represented by each Quad-Tree.
If we apply logical bitwise OR on the two binary matrices we get the binary matrix below which is represented by the result Quad-Tree.
Notice that the binary matrices shown are only for illustration, you don't have to construct the binary matrix to get the result tree.Example 2:
Input: quadTree1 = [[1,0]], quadTree2 = [[1,0]]
Output: [[1,0]]
Explanation: Each tree represents a binary matrix of size 1*1. Each matrix contains only zero.
The resulting matrix is of size 1*1 with also zero.
Constraints:
quadTree1andquadTree2are both valid Quad-Trees each representing an * ngrid.n == 2xwhere0 <= x <= 9.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach is straightforward but inefficient. It involves three main steps: first, convert both input Quad-Trees into their corresponding n x n binary matrices. Second, perform a cell-by-cell logical OR operation on these two matrices to produce a result matrix. Finally, construct a new Quad-Tree from this result matrix. This method is easy to understand but can be slow and memory-intensive, especially for large grids, as it doesn't leverage the compressed nature of Quad-Trees.
Algorithm
- Convert Quad-Tree to Grid: Create a helper function that recursively traverses a Quad-Tree and populates a 2D boolean grid. For a leaf node, it fills the corresponding grid area with the node's value. For an internal node, it recurses on its four children for the four sub-quadrants.
- Get Grids: Call the conversion function for both
quadTree1andquadTree2to obtaingrid1andgrid2. This step requires knowing the grid dimensionn. - Perform Logical OR: Create a new
resultGridof sizen x n. Iterate through every cell(i, j)and computeresultGrid[i][j] = grid1[i][j] || grid2[i][j]. - Convert Grid to Quad-Tree: Create another helper function that recursively builds a Quad-Tree from a grid. For a given grid area, it first checks if all cells have the same value. If so, it creates a leaf node. Otherwise, it creates an internal node and recursively calls itself on the four sub-quadrants.
- Return Result: Call the grid-to-tree conversion function on
resultGridto get the final Quad-Tree.
Walkthrough
The algorithm consists of three main parts:
convertToGrid(Node root, int n): A function to transform a Quad-Tree into a 2D grid. It would internally use a recursive helperfillGrid(Node node, int r, int c, int size, boolean[][] grid). Ifnodeis a leaf, it fills thesize x sizesubgrid starting at(r, c)withnode.val. If it's not a leaf, it divides the currentsize x sizearea into foursize/2 x size/2quadrants and recursively calls itself for each child node on the corresponding quadrant.performOR(grid1, grid2): A function that takes two grids and returns a new grid which is the logical OR of the two. It iterates through each cell(i, j)and computesresult[i][j] = grid1[i][j] || grid2[i][j].buildTree(grid, r, c, size): A recursive function to build a Quad-Tree from a 2D grid. It first checks if thesize x sizesubgrid starting at(r, c)is uniform (all 0s or all 1s). This check takesO(size^2)time. If uniform, it returns a leaf node. If not, it creates an internal node and recursively calls itself for the four sub-quadrants to build the child nodes.
The main function would orchestrate these steps. Note that this approach is generally not recommended due to its poor performance.
// Conceptual implementation of the buildTree helperprivate Node buildTree(boolean[][] grid, int r, int c, int size) { // Check if the subgrid is uniform boolean isUniform = true; boolean firstVal = grid[r][c]; for (int i = r; i < r + size; i++) { for (int j = c; j < c + size; j++) { if (grid[i][j] != firstVal) { isUniform = false; break; } } if (!isUniform) break; } if (isUniform) { return new Node(firstVal, true, null, null, null, null); } else { int newSize = size / 2; Node topLeft = buildTree(grid, r, c, newSize); Node topRight = buildTree(grid, r, c + newSize, newSize); Node bottomLeft = buildTree(grid, r + newSize, c, newSize); Node bottomRight = buildTree(grid, r + newSize, c + newSize, newSize); return new Node(false, false, topLeft, topRight, bottomLeft, bottomRight); }}Complexity
Time
O(n^2) - Converting a Quad-Tree to a grid takes `O(n^2)`. Performing the logical OR on two `n x n` grids takes `O(n^2)`. Building a Quad-Tree from a grid takes `O(n^2)`. The total complexity is `O(n^2) + O(n^2) + O(n^2) = O(n^2)`.
Space
O(n^2) - We need to store at least two `n x n` grids, requiring `O(n^2)` space. The result grid also takes `O(n^2)` space.
Trade-offs
Pros
Conceptually simple and easy to follow.
Breaks the problem down into three distinct, well-understood subproblems.
Cons
Highly inefficient in terms of both time and space.
Requires
O(n^2)space for the grids, which can be very large fornup to 512.Time complexity is
O(n^2), which is slow for largen.It completely ignores the compression benefit of using Quad-Trees, which is their primary purpose.
This approach performs a recursive traversal on both Quad-Trees simultaneously. It constructs the resulting Quad-Tree directly without the need for an intermediate grid representation. The logic cleverly utilizes the properties of the logical OR operation (A OR 1 = 1, A OR 0 = A) and the structure of the Quad-Tree to prune the traversal and merge nodes, leading to a highly efficient solution.
Algorithm
- Define a recursive function
intersect(node1, node2)that returns the resultingNode. - Base Case 1: If
node1is a leaf (node1.isLeaf == true):- If
node1.valistrue, the OR operation results in a grid of alltrue. Return a new leaf node withval = true. - If
node1.valisfalse, the OR operation result is determined bynode2. Returnnode2itself.
- If
- Base Case 2: If
node2is a leaf, apply the symmetric logic from Base Case 1. - Recursive Step: If both
node1andnode2are internal nodes (not leaves):- Recursively call
intersectfor each corresponding pair of children to get the four sub-tree results:topLeft,topRight,bottomLeft,bottomRight. - e.g.,
topLeft = intersect(node1.topLeft, node2.topLeft)
- Recursively call
- Merge Step: After the recursive calls return, check if the four resulting children nodes can be merged. This is possible if all four are leaf nodes and all have the same
val.- If they can be merged, return a new single leaf node with that common value.
- Otherwise, return a new internal node with the four children nodes obtained from the recursive calls.
- The initial call to the function will be
intersect(quadTree1, quadTree2).
Walkthrough
The core of this approach is a recursive function, intersect(node1, node2), which computes the Quad-Tree node for the logical OR of the regions represented by node1 and node2.
The recursion follows these rules:
- Pruning with Leaf Nodes: If one of the nodes, say
node1, is a leaf, we can quickly determine the result. Ifnode1.valistrue(a grid of 1s), the OR result is also a grid of 1s, so we return a new leaf node withval = true. Ifnode1.valisfalse(a grid of 0s), the OR result is identical to the grid represented bynode2, so we can simply returnnode2. - Recursive Descent: If both
node1andnode2are internal nodes, we must recurse deeper. We callintersecton each pair of corresponding children (topLeftwithtopLeft,topRightwithtopRight, etc.). - Merging Children: After the four recursive calls for the children return, we check if the resulting four sub-trees can be merged. If all four are leaf nodes and share the same value, they represent a uniform quadrant. We can merge them by creating a single new leaf node with that common value. Otherwise, we create a new internal node and assign the four sub-trees as its children.
This process efficiently builds the result tree from the bottom up, merging quadrants whenever possible.
class Solution { public Node intersect(Node quadTree1, Node quadTree2) { // If quadTree1 is a leaf node if (quadTree1.isLeaf) { // If its value is true (1), the OR result is a leaf with value true. if (quadTree1.val) { return new Node(true, true, null, null, null, null); } // If its value is false (0), the OR result is simply quadTree2. return quadTree2; } // If quadTree2 is a leaf node (symmetric case) if (quadTree2.isLeaf) { if (quadTree2.val) { return new Node(true, true, null, null, null, null); } return quadTree1; } // If both are internal nodes, recurse on their children Node tl = intersect(quadTree1.topLeft, quadTree2.topLeft); Node tr = intersect(quadTree1.topRight, quadTree2.topRight); Node bl = intersect(quadTree1.bottomLeft, quadTree2.bottomLeft); Node br = intersect(quadTree1.bottomRight, quadTree2.bottomRight); // Check if all children are leaves and have the same value to merge them if (tl.isLeaf && tr.isLeaf && bl.isLeaf && br.isLeaf && tl.val == tr.val && tr.val == bl.val && bl.val == br.val) { // If so, merge them into a single leaf node return new Node(tl.val, true, null, null, null, null); } else { // Otherwise, create a new internal node with these children return new Node(false, false, tl, tr, bl, br); } }}Complexity
Time
O(N1 + N2) - where `N1` and `N2` are the number of nodes in `quadTree1` and `quadTree2` respectively. In the worst case, we might visit every node in both trees. Each recursive call involves a constant amount of work, so the time is proportional to the number of nodes visited.
Space
O(N1 + N2) - The space is used for the recursion stack and the new tree being built. The recursion depth is at most `log(n)`, so stack space is `O(log n)`. The space for the new tree is proportional to its number of nodes, which is at most `O(N1 + N2)`. Thus, the total space is `O(N1 + N2)`.
Trade-offs
Pros
Highly efficient, especially for grids with large uniform areas.
Avoids the overhead of creating and processing large intermediate matrices.
Time and space complexity are proportional to the number of nodes in the trees, not the size of the grid.
Cons
The recursive logic can be slightly more complex to grasp initially compared to the brute-force method.
Solutions
Solution
/* // Definition for a QuadTree node. class Node { public boolean val; public boolean isLeaf; public Node topLeft; public Node topRight; public Node bottomLeft; public Node bottomRight; public Node() {} public Node(boolean _val,boolean _isLeaf,Node _topLeft,Node _topRight,Node _bottomLeft,Node _bottomRight) { val = _val; isLeaf = _isLeaf; topLeft = _topLeft; topRight = _topRight; bottomLeft = _bottomLeft; bottomRight = _bottomRight; } }; */ class Solution { public Node intersect ( Node quadTree1 , Node quadTree2 ) { return dfs ( quadTree1 , quadTree2 ); } private Node dfs ( Node t1 , Node t2 ) { if ( t1 . isLeaf && t2 . isLeaf ) { return new Node ( t1 . val || t2 . val , true ); } if ( t1 . isLeaf ) { return t1 . val ? t1 : t2 ; } if ( t2 . isLeaf ) { return t2 . val ? t2 : t1 ; } Node res = new Node (); res . topLeft = dfs ( t1 . topLeft , t2 . topLeft ); res . topRight = dfs ( t1 . topRight , t2 . topRight ); res . bottomLeft = dfs ( t1 . bottomLeft , t2 . bottomLeft ); res . bottomRight = dfs ( t1 . bottomRight , t2 . bottomRight ); boolean isLeaf = res . topLeft . isLeaf && res . topRight . isLeaf && res . bottomLeft . isLeaf && res . bottomRight . isLeaf ; boolean sameVal = res . topLeft . val == res . topRight . val && res . topRight . val == res . bottomLeft . val && res . bottomLeft . val == res . bottomRight . val ; if ( isLeaf && sameVal ) { res = res . topLeft ; } return res ; } }/* // Definition for a QuadTree node. class Node { public: bool val; bool isLeaf; Node* topLeft; Node* topRight; Node* bottomLeft; Node* bottomRight; Node() { val = false; isLeaf = false; topLeft = NULL; topRight = NULL; bottomLeft = NULL; bottomRight = NULL; } Node(bool _val, bool _isLeaf) { val = _val; isLeaf = _isLeaf; topLeft = NULL; topRight = NULL; bottomLeft = NULL; bottomRight = NULL; } Node(bool _val, bool _isLeaf, Node* _topLeft, Node* _topRight, Node* _bottomLeft, Node* _bottomRight) { val = _val; isLeaf = _isLeaf; topLeft = _topLeft; topRight = _topRight; bottomLeft = _bottomLeft; bottomRight = _bottomRight; } }; */ class Solution { public: Node * intersect ( Node * quadTree1 , Node * quadTree2 ) { return dfs ( quadTree1 , quadTree2 ); } Node * dfs ( Node * t1 , Node * t2 ) { if ( t1 -> isLeaf && t2 -> isLeaf ) return new Node ( t1 -> val || t2 -> val , true ); if ( t1 -> isLeaf ) return t1 -> val ? t1 : t2 ; if ( t2 -> isLeaf ) return t2 -> val ? t2 : t1 ; Node * res = new Node (); res -> topLeft = dfs ( t1 -> topLeft , t2 -> topLeft ); res -> topRight = dfs ( t1 -> topRight , t2 -> topRight ); res -> bottomLeft = dfs ( t1 -> bottomLeft , t2 -> bottomLeft ); res -> bottomRight = dfs ( t1 -> bottomRight , t2 -> bottomRight ); bool isLeaf = res -> topLeft -> isLeaf && res -> topRight -> isLeaf && res -> bottomLeft -> isLeaf && res -> bottomRight -> isLeaf ; bool sameVal = res -> topLeft -> val == res -> topRight -> val && res -> topRight -> val == res -> bottomLeft -> val && res -> bottomLeft -> val == res -> bottomRight -> val ; if ( isLeaf && sameVal ) res = res -> topLeft ; return res ; } };""" # Definition for a QuadTree node. class Node: def __init__(self, val, isLeaf, topLeft, topRight, bottomLeft, bottomRight): self.val = val self.isLeaf = isLeaf self.topLeft = topLeft self.topRight = topRight self.bottomLeft = bottomLeft self.bottomRight = bottomRight """ class Solution : def intersect ( self , quadTree1 : "Node" , quadTree2 : "Node" ) -> "Node" : def dfs ( t1 , t2 ): if t1 . isLeaf and t2 . isLeaf : return Node ( t1 . val or t2 . val , True ) if t1 . isLeaf : return t1 if t1 . val else t2 if t2 . isLeaf : return t2 if t2 . val else t1 res = Node () res . topLeft = dfs ( t1 . topLeft , t2 . topLeft ) res . topRight = dfs ( t1 . topRight , t2 . topRight ) res . bottomLeft = dfs ( t1 . bottomLeft , t2 . bottomLeft ) res . bottomRight = dfs ( t1 . bottomRight , t2 . bottomRight ) isLeaf = ( res . topLeft . isLeaf and res . topRight . isLeaf and res . bottomLeft . isLeaf and res . bottomRight . isLeaf ) sameVal = ( res . topLeft . val == res . topRight . val == res . bottomLeft . val == res . bottomRight . val ) if isLeaf and sameVal : res = res . topLeft return res return dfs ( quadTree1 , quadTree2 )Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.