建立四叉树

标签: 数组 分治 矩阵

难度: Medium

给你一个 n * n 矩阵 grid ,矩阵由若干 01 组成。请你用四叉树表示该矩阵 grid

你需要返回能表示矩阵 grid 的 四叉树 的根结点。

四叉树数据结构中,每个内部节点只有四个子节点。此外,每个节点都有两个属性:

  • val:储存叶子结点所代表的区域的值。1 对应 True,0 对应 False。注意,当 isLeafFalse 时,你可以把 True 或者 False 赋值给节点,两种值都会被判题机制 接受
  • isLeaf: 当这个节点是一个叶子结点时为 True,如果它有 4 个子节点则为 False
class Node {
    public boolean val;
    public boolean isLeaf;
    public Node topLeft;
    public Node topRight;
    public Node bottomLeft;
    public Node bottomRight;
}

我们可以按以下步骤为二维区域构建四叉树:

  1. 如果当前网格的值相同(即,全为 0 或者全为 1),将 isLeaf 设为 True ,将 val 设为网格相应的值,并将四个子节点都设为 Null 然后停止。
  2. 如果当前网格的值不同,将 isLeaf 设为 False, 将 val 设为任意值,然后如下图所示,将当前网格划分为四个子网格。
  3. 使用适当的子网格递归每个子节点。

如果你想了解更多关于四叉树的内容,可以参考 wiki

四叉树格式:

你不需要阅读本节来解决这个问题。只有当你想了解输出格式时才会这样做。输出为使用层序遍历后四叉树的序列化形式,其中 null 表示路径终止符,其下面不存在节点。

它与二叉树的序列化非常相似。唯一的区别是节点以列表形式表示 [isLeaf, val]

如果 isLeaf 或者 val 的值为 True ,则表示它在列表 [isLeaf, val] 中的值为 1 ;如果 isLeaf 或者 val 的值为 False ,则表示值为 0

示例 1:

输入:grid = [[0,1],[1,0]]
输出:[[0,1],[1,0],[1,1],[1,1],[1,0]]
解释:此示例的解释如下:
请注意,在下面四叉树的图示中,0 表示 false,1 表示 True 。

示例 2:

输入:grid = [[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0],[1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1],[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0]]
输出:[[0,1],[1,1],[0,1],[1,1],[1,0],null,null,null,null,[1,0],[1,0],[1,1],[1,1]]
解释:网格中的所有值都不相同。我们将网格划分为四个子网格。
topLeft,bottomLeft 和 bottomRight 均具有相同的值。
topRight 具有不同的值,因此我们将其再分为 4 个子网格,这样每个子网格都具有相同的值。
解释如下图所示:

提示:

  1. n == grid.length == grid[i].length
  2. n == 2x 其中 0 <= x <= 6

Submission

运行时间: 69 ms

内存: 16.8 MB

"""
# 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 construct(self, grid: List[List[int]]) -> 'Node':
        def is_leaf(x, y, length):
            num = grid[x][y]
            for i in range(x, x+length):
                for j in range(y, y+length):
                    if num != grid[i][j]:
                        return False
            return True
        
        def construct_helper(x, y, length):
            val = is_leaf(x, y, length)
            if val:
                return Node(grid[x][y]==1, True, None,None,None,None)
            
            half = length // 2
            top_left = construct_helper(x, y, half)
            top_right = construct_helper(x, y+half, half)
            bottom_left = construct_helper(x+half, y, half)
            bottom_right = construct_helper(x+half, y+half, half)
            return Node(False, False, top_left, top_right, bottom_left, bottom_right)
        return construct_helper(0, 0, len(grid))
        

Explain

这个题解使用分治法来构建四叉树。从根节点开始,如果当前网格的值都相同,则将其设为叶子节点,值为网格的值。否则将网格划分为四个子网格,递归地构建每个子节点。

时间复杂度: O(n^4)

空间复杂度: O(log(n))

class Solution:
    def construct(self, grid: List[List[int]]) -> 'Node':
        def is_leaf(x, y, length):
            # 检查子网格的元素是否全部相同
            num = grid[x][y]
            for i in range(x, x+length):
                for j in range(y, y+length):
                    if num != grid[i][j]:
                        return False
            return True
        
        def construct_helper(x, y, length):
            # 如果子网格元素全部相同,将其设为叶子节点
            val = is_leaf(x, y, length)
            if val:
                return Node(grid[x][y]==1, True, None,None,None,None)
            
            # 否则划分成四个子网格,递归构建子节点
            half = length // 2
            top_left = construct_helper(x, y, half)
            top_right = construct_helper(x, y+half, half)
            bottom_left = construct_helper(x+half, y, half)
            bottom_right = construct_helper(x+half, y+half, half)
            return Node(False, False, top_left, top_right, bottom_left, bottom_right)
        
        return construct_helper(0, 0, len(grid))

Explore

在函数`is_leaf`中,遍历所有元素来确定是否所有值相同是一个时间复杂度为O(n^2)的操作,对于较大的矩阵效率较低。一个更优的方法是使用辅助的数据结构,如差分数组或者二维前缀和数组,这可以在O(1)时间内判断一个子矩阵中的元素是否全部相同,但需要O(n^2)的时间和空间来预处理这些数据结构。

题解中提到的递归层数为`log(n)`是基于每次递归将网格的边长减半,而不是网格的面积。每次递归处理时,网格从n变为n/2, n/4, ..., 直到1,所以递归层数是`log2(n)`。尽管每次递归生成四个子网格,这些子网格是同时处理的,并不会影响递归的最大深度,因此递归层数是`log(n)`而不是`2*log(n)`。

在四叉树中,非叶子节点的`val`属性被设置为`False`主要是为了表明该节点不代表具体的值,而是一个结构性的节点,其主要的作用是连接其子节点。设置为`True`或者根据子节点的值可能会引起误解,使得节点的值在逻辑上变得不清晰。非叶子节点的主要功能是组织和结构化数据,而不是存储数据值。

即使矩阵大小`n`不是2的幂次方,递归分割方法仍然适用。在这种情况下,可以将矩阵扩展到最近的2的幂次方大小,未覆盖的区域可以用默认值填充(如0)。这种方法允许均匀分割而不需要修改递归逻辑。另一种方法是在每次递归时动态计算子矩阵的大小,使得较小的子矩阵仍能被处理,即使它们的大小不完全相等。