Skip to content

Commit 9b2a56c

Browse files
committed
LeetCode 427. Construct Quad Tree
1 parent f35d874 commit 9b2a56c

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ Solutions to LeetCode problems. The first column links to the problem in LeetCod
218218
| [416. Partition Equal Subset Sum][lc416] | 🟠 Medium | [![python](res/py.png)][lc416py] |
219219
| [417. Pacific Atlantic Water Flow][lc417] | 🟢 Easy | [![python](res/py.png)][lc417py] |
220220
| [424. Longest Repeating Character Replacement][lc424] | 🟠 Medium | [![python](res/py.png)][lc424py] |
221+
| [427. Construct Quad Tree][lc427] | 🟠 Medium | [![python](res/py.png)][lc427py] |
221222
| [429. N-ary Tree Level Order Traversal][lc429] | 🟠 Medium | [![python](res/py.png)][lc429py] |
222223
| [433. Minimum Genetic Mutation][lc433] | 🟠 Medium | [![python](res/py.png)][lc433py] |
223224
| [435. Non-overlapping Intervals][lc435] | 🟠 Medium | [![python](res/py.png)][lc435py] |
@@ -856,6 +857,8 @@ Solutions to LeetCode problems. The first column links to the problem in LeetCod
856857
[lc417py]: leetcode/pacific-atlantic-water-flow.py
857858
[lc424]: https://leetcode.com/problems/longest-repeating-character-replacement/
858859
[lc424py]: leetcode/longest-repeating-character-replacement.py
860+
[lc427]: https://leetcode.com/problems/construct-quad-tree/
861+
[lc427py]: leetcode/construct-quad-tree.py
859862
[lc429]: https://leetcode.com/problems/n-ary-tree-level-order-traversal/
860863
[lc429py]: https://leetcode.com/problems/n-ary-tree-level-order-traversal/
861864
[lc433]: https://leetcode.com/problems/minimum-genetic-mutation/

leetcode/construct-quad-tree.py

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# 427. Construct Quad Tree
2+
# 🟠 Medium
3+
#
4+
# https://leetcode.com/problems/construct-quad-tree/
5+
#
6+
# Tags: Array - Divide and Conquer - Tree - Matrix
7+
8+
import timeit
9+
from typing import List
10+
11+
12+
# Definition for a QuadTree node.
13+
class Node:
14+
def __init__(
15+
self, val, isLeaf, topLeft, topRight, bottomLeft, bottomRight
16+
):
17+
self.val = val
18+
self.isLeaf = isLeaf
19+
self.topLeft = topLeft
20+
self.topRight = topRight
21+
self.bottomLeft = bottomLeft
22+
self.bottomRight = bottomRight
23+
24+
25+
# We can create an internal function that takes the boundaries of a
26+
# section of the input matrix, can be the entire matrix, and returns the
27+
# quad node that represents the root of the section. One way to do it
28+
# would be to iterate all the cells in the given section checking if
29+
# they all contain the same value, if they do, the current node is a
30+
# leaf. Another way to do it, is to recursively divide the grid in
31+
# nodes until we get to sections of size 1, which by definition must be
32+
# leaves, then start reconstructing the tree from the roots, when a node
33+
# has all leaf children and they all have the same value, that node
34+
# becomes a leave itself.
35+
#
36+
# Time complexity: O(n^2) - We will visit each cell in the matrix once.
37+
# Space complexity: O(n^2) - We may create a node for each cell in the
38+
# matrix. The call stack will be log(n) as well but that is simplified.
39+
#
40+
# Runtime 130 ms Beats 38.29%
41+
# Memory 14.8 MB Beats 69.97%
42+
class AllocateMemory:
43+
def construct(self, grid: List[List[int]]) -> Node:
44+
# A function that takes a top corner and a size and returns the
45+
# quad node for the section of the matrix that it defines.
46+
def getNode(top: int, l: int, n: int) -> Node:
47+
# Base case, a node with a single cell is a leaf.
48+
if n == 1:
49+
return Node(bool(grid[top][l]), True)
50+
# Otherwise split in 4 and compute its leaves.
51+
s = n // 2
52+
tl = getNode(top, l, s)
53+
tr = getNode(top, l + s, s)
54+
bl = getNode(top + s, l, s)
55+
br = getNode(top + s, l + s, s)
56+
57+
# Check if we can convert this node to a leave.
58+
children = (tl, tr, bl, br)
59+
if all([c.isLeaf for c in children]) and (
60+
all([c.val for c in children])
61+
or all([not c.val for c in children])
62+
):
63+
return Node(tl.val, True)
64+
return Node(True, False, tl, tr, bl, br)
65+
66+
return getNode(0, 0, len(grid))
67+
68+
69+
# Similar approach to the previous solution but, instead of creating a
70+
# node instance for each leave, we create two, false and true, and
71+
# reuse one of them for each leave node.
72+
#
73+
# Time complexity: O(n^2) - We will visit each cell in the matrix once.
74+
# Space complexity: O(n^2) - We may create a node each for non-leave
75+
# cell in the matrix, which still has a linear relation with the number
76+
# of cells, even though we are reusing nodes for leaves.
77+
#
78+
# Runtime 109 ms Beats 89.53%
79+
# Memory 14.8 MB Beats 69.97%
80+
class ReuseMemory:
81+
def construct(self, grid: List[List[int]]) -> Node:
82+
# Create two leaf nodes that can be reused.
83+
falseLeaf = Node(False, True)
84+
trueLeaf = Node(True, True)
85+
# A function that takes a top corner and a size and returns the
86+
# quad node for the section of the matrix that it defines.
87+
def getNode(top: int, l: int, n: int) -> Node:
88+
# Base case, a node with a single cell is a leaf.
89+
if n == 1:
90+
return trueLeaf if grid[top][l] else falseLeaf
91+
# Otherwise split in 4 and compute its leaves.
92+
s = n // 2
93+
tl = getNode(top, l, s)
94+
tr = getNode(top, l + s, s)
95+
bl = getNode(top + s, l, s)
96+
br = getNode(top + s, l + s, s)
97+
98+
# Check if we can convert this node to a leave.
99+
children = (tl, tr, bl, br)
100+
if all([c.isLeaf for c in children]) and (
101+
all([c.val for c in children])
102+
or all([not c.val for c in children])
103+
):
104+
return trueLeaf if tl.val else falseLeaf
105+
return Node(True, False, tl, tr, bl, br)
106+
107+
return getNode(0, 0, len(grid))
108+
109+
110+
def test():
111+
executors = [
112+
AllocateMemory,
113+
ReuseMemory,
114+
]
115+
tests = []
116+
for executor in executors:
117+
start = timeit.default_timer()
118+
for _ in range(1):
119+
for col, t in enumerate(tests):
120+
sol = executor()
121+
result = sol.construct(t[0])
122+
exp = t[1]
123+
assert result == exp, (
124+
f"\033[93m» {result} <> {exp}\033[91m for"
125+
+ f" test {col} using \033[1m{executor.__name__}"
126+
)
127+
stop = timeit.default_timer()
128+
used = str(round(stop - start, 5))
129+
cols = "{0:20}{1:10}{2:10}"
130+
res = cols.format(executor.__name__, used, "seconds")
131+
print(f"\033[92m» {res}\033[0m")
132+
133+
134+
# test()
135+
print(f"\033[93m» This file does not have any tests!\033[0m")

0 commit comments

Comments
 (0)