分治题目:建立四叉树

题目

标题和出处

标题:建立四叉树

出处:427. 建立四叉树

难度

5 级

题目描述

要求

给定一个 n × n \texttt{n} \times \texttt{n} n×n 的矩阵 grid \texttt{grid} grid,矩阵由若干 0 \texttt{0} 0 1 \texttt{1} 1 组成。要求用四叉树表示该矩阵 grid \texttt{grid} grid

返回能表示 grid \texttt{grid} grid 的四叉树的根结点。

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

  • val \texttt{val} val:储存叶结点所代表的区域的值, 1 \texttt{1} 1 对应 true \texttt{true} true 0 \texttt{0} 0 对应 false \texttt{false} false
  • isLeaf \texttt{isLeaf} isLeaf:当这个结点是叶结点时为 true \texttt{true} true,当这个结点有 4 \texttt{4} 4 个子结点时为 false \texttt{false} false

注意,当 isLeaf \texttt{isLeaf} isLeaf false \texttt{false} false 时,可以把 true \texttt{true} true 或者 false \texttt{false} false 赋值给 val \texttt{val} val,两种值都是允许的。

class Node {
    public boolean val;
    public boolean isLeaf;
    public Node topLeft;
    public Node topRight;
    public Node bottomLeft;
    public Node bottomRight;
}

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

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

示例 0

四叉树格式

输出为使用层序遍历后四叉树的序列化形式,其中 null \texttt{null} null 表示路径终止符,其下面不存在结点。

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

如果 isLeaf \texttt{isLeaf} isLeaf 或者 val \texttt{val} val 的值为 true \texttt{true} true,则表示它在列表 [isLeaf,   val] \texttt{[isLeaf, val]} [isLeaf, val] 中的值为 1 \texttt{1} 1;如果 isLeaf \texttt{isLeaf} isLeaf 或者 val \texttt{val} val 的值为 false \texttt{false} false,则表示值为 0 \texttt{0} 0

示例

示例 1:

示例 1-1

输入: grid   =   [[0,1],[1,0]] \texttt{grid = [[0,1],[1,0]]} grid = [[0,1],[1,0]]
输出: [[0,1],[1,0],[1,1],[1,1],[1,0]] \texttt{[[0,1],[1,0],[1,1],[1,1],[1,0]]} [[0,1],[1,0],[1,1],[1,1],[1,0]]
解释:此示例的解释如下:
请注意,在下面四叉树的图示中, 0 \texttt{0} 0 表示 false \texttt{false} false 1 \texttt{1} 1 表示 true \texttt{true} true
示例 1-2

示例 2:

示例 2-1

输入: 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]] \texttt{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]]} 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]] \texttt{[[0,1],[1,1],[0,1],[1,1],[1,0],null,null,null,null,[1,0],[1,0],[1,1],[1,1]]} [[0,1],[1,1],[0,1],[1,1],[1,0],null,null,null,null,[1,0],[1,0],[1,1],[1,1]]
解释:网格中的所有值都不相同。我们将网格划分为四个子网格。
topLeft \texttt{topLeft} topLeft bottomLeft \texttt{bottomLeft} bottomLeft bottomRight \texttt{bottomRight} bottomRight 均具有相同的值。
topRight \texttt{topRight} topRight 具有不同的值,因此我们将其再分为 4 \texttt{4} 4 个子网格,这样每个子网格都具有相同的值。
解释如下图所示:
示例 2-2

数据范围

  • n = grid.length = grid[i].length \texttt{n} = \texttt{grid.length} = \texttt{grid[i].length} n=grid.length=grid[i].length
  • n = 2 x \texttt{n} = \texttt{2}^\texttt{x} n=2x,其中 0 ≤ x ≤ 6 \texttt{0} \le \texttt{x} \le \texttt{6} 0x6

解法

思路和算法

根据定义,四叉树中的每个结点都对应一个矩阵,矩阵的行数和列数相同且为 2 2 2 的非负整数次幂,每个结点的情况如下。

  • 如果一个结点对应的矩阵的边长等于 1 1 1,则该结点是叶结点,结点值为矩阵中的唯一元素值。

  • 如果一个结点对应的矩阵的边长大于 1 1 1,则该结点可能是叶结点或非叶结点。如果该结点的四个子结点都是叶结点且值相同,则该结点是叶结点,否则该结点是非叶结点。

由于题目规定非叶结点的值可以任取,因此判断一个结点是否为叶结点时需要同时考虑其四个子结点是否为叶结点和四个子结点的值是否相同。此处将非叶结点的值设为其四个子结点的值的逻辑或运算的结果。

使用边长为 n n n 的矩阵 grid \textit{grid} grid 构造四叉树时,如果当前结点对应的矩阵边长大于 1 1 1,则首先构建当前结点的四个子结点,然后根据子结点的值构建当前结点。这是一个递归分治的过程。

分治的终止条件是 n = 1 n = 1 n=1,此时四叉树中唯一的结点是叶结点,结点值为矩阵中的唯一元素值。当 n > 1 n > 1 n>1 时,递归地构建当前结点的四个子结点,然后根据四个子结点构建当前结点,构建当前结点的做法如下。

  • 如果四个子结点都是叶结点且值相同,则当前结点是叶结点,其值为四个子结点值的逻辑或运算的结果,将当前结点的子结点设为空。

  • 否则,当前结点不是叶结点,其值为四个子结点值的逻辑或运算的结果,将四个子结点作为当前结点的四个子结点。

使用原始矩阵 grid \textit{grid} grid 根据上述做法构建四叉树,即可得到完整的四叉树。

代码

class Solution {
    public Node construct(int[][] grid) {
        return construct(grid, 0, 0, grid.length);
    }

    public Node construct(int[][] grid, int startRow, int startCol, int side) {
        if (side == 1) {
            return new Node(grid[startRow][startCol] == 1, true);
        }
        int halfSide = side / 2;
        Node topLeft = construct(grid, startRow, startCol, halfSide);
        Node topRight = construct(grid, startRow, startCol + halfSide, halfSide);
        Node bottomLeft = construct(grid, startRow + halfSide, startCol, halfSide);
        Node bottomRight = construct(grid, startRow + halfSide, startCol + halfSide, halfSide);
        boolean val = topLeft.val || topRight.val || bottomLeft.val || bottomRight.val;
        boolean isLeaf = topLeft.isLeaf && topRight.isLeaf && bottomLeft.isLeaf && bottomRight.isLeaf && topLeft.val == topRight.val && topLeft.val == bottomLeft.val && topLeft.val == bottomRight.val;
        return isLeaf ? new Node(val, isLeaf) : new Node(val, isLeaf, topLeft, topRight, bottomLeft, bottomRight);
    }
}

复杂度分析

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n n n 是矩阵 grid \textit{grid} grid 的边长。分治的递归调用栈共有 O ( log ⁡ n ) O(\log n) O(logn) 层,第 k k k 层调用时需要处理的结点数是 O ( 4 k ) O(4^k) O(4k),需要处理的结点总数是 O ( n 2 ) O(n^2) O(n2),每个结点的处理时间是 O ( 1 ) O(1) O(1),因此时间复杂度是 O ( n 2 ) O(n^2) O(n2)

  • 空间复杂度: O ( log ⁡ n ) O(\log n) O(logn),其中 n n n 是矩阵 grid \textit{grid} grid 的边长。递归调用栈需要 O ( log ⁡ n ) O(\log n) O(logn) 的空间。注意返回值不计入空间复杂度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值