leetcode
leetcode 651 ~ 700
建立四叉树

建立四叉树

难度:

标签:

题目描述

给你一个 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

代码结果

运行时间: 69 ms, 内存: 16.8 MB


/*
 * This solution constructs a QuadTree from a given n*n grid using Java Streams.
 * The logic follows the same recursive approach as the traditional Java solution.
 * Here, streams are used to handle recursive calls and collect results.
 */
import java.util.stream.IntStream;
 
public class SolutionStream {
    public Node construct(int[][] grid) {
        return construct(grid, 0, 0, grid.length);
    }
 
    private Node construct(int[][] grid, int x, int y, int length) {
        if (length == 1) {
            return new Node(grid[x][y] == 1, true);
        }
 
        int half = length / 2;
        Node[] nodes = IntStream.of(0, 1).boxed().flatMap(i -> IntStream.of(0, 1).mapToObj(j -> construct(grid, x + i * half, y + j * half, half))).toArray(Node[]::new);
 
        if (nodes[0].isLeaf && nodes[1].isLeaf && nodes[2].isLeaf && nodes[3].isLeaf &&
            nodes[0].val == nodes[1].val && nodes[1].val == nodes[2].val && nodes[2].val == nodes[3].val) {
            return new Node(nodes[0].val, true);
        }
 
        return new Node(false, false, nodes[0], nodes[1], nodes[2], nodes[3]);
    }
}

解释

方法:

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

时间复杂度:

O(n^4)

空间复杂度:

O(log(n))

代码细节讲解

🦆
在判断是否为叶子节点的函数`is_leaf`中,对于较大的矩阵,遍历所有元素来确定是否所有值相同的方法效率如何?是否有更优的方法来减少遍历次数?
在函数`is_leaf`中,遍历所有元素来确定是否所有值相同是一个时间复杂度为O(n^2)的操作,对于较大的矩阵效率较低。一个更优的方法是使用辅助的数据结构,如差分数组或者二维前缀和数组,这可以在O(1)时间内判断一个子矩阵中的元素是否全部相同,但需要O(n^2)的时间和空间来预处理这些数据结构。
🦆
题解中提到的递归层数最多为`log(n)`,这个结论是如何得出的?考虑到每次递归网格都被分为四个更小的网格,递归深度是否应该是`log(n)`的两倍?
题解中提到的递归层数为`log(n)`是基于每次递归将网格的边长减半,而不是网格的面积。每次递归处理时,网格从n变为n/2, n/4, ..., 直到1,所以递归层数是`log2(n)`。尽管每次递归生成四个子网格,这些子网格是同时处理的,并不会影响递归的最大深度,因此递归层数是`log(n)`而不是`2*log(n)`。
🦆
节点的`val`属性在非叶子节点的情况下被设置为`False`,这样做有什么特别的意义吗?为什么不设置为`True`或者根据子节点的值来定义?
在四叉树中,非叶子节点的`val`属性被设置为`False`主要是为了表明该节点不代表具体的值,而是一个结构性的节点,其主要的作用是连接其子节点。设置为`True`或者根据子节点的值可能会引起误解,使得节点的值在逻辑上变得不清晰。非叶子节点的主要功能是组织和结构化数据,而不是存储数据值。
🦆
在构建四叉树时,如果矩阵的大小`n`不是2的幂次方,这种递归分割方法还适用吗?如何处理那些不能均匀分割的情况?
即使矩阵大小`n`不是2的幂次方,递归分割方法仍然适用。在这种情况下,可以将矩阵扩展到最近的2的幂次方大小,未覆盖的区域可以用默认值填充(如0)。这种方法允许均匀分割而不需要修改递归逻辑。另一种方法是在每次递归时动态计算子矩阵的大小,使得较小的子矩阵仍能被处理,即使它们的大小不完全相等。

相关问题