|
| 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