LeetCode 427 - 建立四叉树


文章目录

摘要

四叉树(QuadTree)这个结构在日常开发中并不常见,但它其实在图像处理、地图分块、区域搜索等领域非常常用。这道题让我们根据一个 n × n 的 0/1 网格去构建对应的四叉树结构。

核心思想其实就是一句话:

如果区域内所有值相同,就是叶子节点;否则,把区域继续分成四块,递归构建。

虽然概念简单,但写代码时容易在"坐标"和"区域分割"上打结。本文会用口语化的方式带你拆清楚每一步,并提供一个可运行的 Swift Demo,让你完全掌握四叉树构建的套路。

描述

题目给了我们一个正方形矩阵 grid,里面只有 0 和 1。我们需要用"四叉树"(QuadTree)来表示这块区域。

每个节点有两个关键属性:

  • isLeaf:是否是叶子节点
  • val:叶子节点的值(0/1 对应 false/true)

如果当前区域全是一样的数字,比如全是 1 或全是 0,那就直接生成一个叶子节点。

如果区域内包含 0 和 1 混合,那就把区域划分成四个子区域:

txt 复制代码
topLeft      topRight
bottomLeft   bottomRight

然后对四块区域分别递归构建四叉树。

最终返回四叉树的根节点。

题解答案(简要思路)

解决这题的核心流程如下:

  1. 编写一个函数 build(x, y, size)

    它用于构建以 (x, y) 为左上角、边长为 size 的区域。

  2. 在这个区域里检查是否全为相同的数:

    • 如果是:直接返回叶子节点
    • 如果不是:继续分为四块
  3. 递归构建四个子节点

  4. 返回内部节点

常见容易出错的点:

  • 区域分割的坐标计算是否正确
  • 递归终止条件是否写对
  • 数组访问是否越界
  • 节点的 val 在非叶节点时无所谓,但习惯设为 true/false 均可

下面我们直接进入完整 Swift Demo,之后再做详细拆解。

题解代码分析(含可运行 Demo)

下面这段代码可以直接运行,包括:

  • Node 定义(Swift 版本)
  • 构建四叉树的递归逻辑
  • 工具函数:打印树结构(方便调试)
  • 示例测试代码

Swift 可运行代码

swift 复制代码
import Foundation

class Node {
    var val: Bool
    var isLeaf: Bool
    var topLeft: Node?
    var topRight: Node?
    var bottomLeft: Node?
    var bottomRight: Node?

    init(_ val: Bool, _ isLeaf: Bool) {
        self.val = val
        self.isLeaf = isLeaf
    }
}

class Solution {
    func construct(_ grid: [[Int]]) -> Node? {
        return build(grid, 0, 0, grid.count)
    }

    private func build(_ grid: [[Int]], _ x: Int, _ y: Int, _ size: Int) -> Node {
        // 判断当前区域是否统一
        if isUniform(grid, x, y, size) {
            let val = grid[x][y] == 1
            return Node(val, true)
        }

        // 区域不统一,继续拆分
        let half = size / 2
        let topLeft = build(grid, x, y, half)
        let topRight = build(grid, x, y + half, half)
        let bottomLeft = build(grid, x + half, y, half)
        let bottomRight = build(grid, x + half, y + half, half)

        let node = Node(true, false) // val 无所谓
        node.topLeft = topLeft
        node.topRight = topRight
        node.bottomLeft = bottomLeft
        node.bottomRight = bottomRight
        return node
    }

    private func isUniform(_ grid: [[Int]], _ x: Int, _ y: Int, _ size: Int) -> Bool {
        let first = grid[x][y]
        for i in x..<x+size {
            for j in y..<y+size {
                if grid[i][j] != first {
                    return false
                }
            }
        }
        return true
    }
}

// MARK: 工具:打印四叉树(简单文本结构)
func printQuadTree(_ node: Node?, _ indent: String = "") {
    guard let node else { return }
    if node.isLeaf {
        print("\(indent)Leaf: \(node.val ? 1 : 0)")
    } else {
        print("\(indent)Node")
        printQuadTree(node.topLeft, indent + "  ")
        printQuadTree(node.topRight, indent + "  ")
        printQuadTree(node.bottomLeft, indent + "  ")
        printQuadTree(node.bottomRight, indent + "  ")
    }
}

代码解析(逐段讲解)

1. 为什么要用 build(x, y, size) 这种递归形式?

因为我们每次处理的都是 grid 的子矩阵 ,所以需要用左上角坐标 (x, y) 和区域大小 size 来定位子区域。

这样分割区域时非常清晰:

txt 复制代码
当前区域:(x, y), size
四个子区域:

topLeft:     (x, y), size/2
topRight:    (x, y+half)
bottomLeft:  (x+half, y)
bottomRight: (x+half, y+half)

不用重新创建子矩阵,效率更高。

2. 判断区域是否统一

swift 复制代码
private func isUniform(_ grid: [[Int]], _ x: Int, _ y: Int, _ size: Int) -> Bool 

这个函数非常关键,它检查该区域是否全 0 或全 1。

如果统一,那这个区域就可以变成一个叶子节点。

在构建 quadtree 的过程中,这一步是性能瓶颈之一,但输入规模较小(最大 64 × 64),所以完全没问题。

3. 构建内部节点

如果不是叶子节点:

swift 复制代码
let node = Node(true, false)

这里 val 随便设,因为内部节点的 val 不影响结果(判题机制就是这么设计的)。

然后把四个子节点挂上去:

swift 复制代码
node.topLeft = topLeft
node.topRight = topRight
node.bottomLeft = bottomLeft
node.bottomRight = bottomRight

流程非常自然。

示例测试及结果

我们用一个简单的例子来测试:

txt 复制代码
grid = [
  [1, 1],
  [1, 1]
]

测试代码:

swift 复制代码
let grid = [
    [1, 1],
    [1, 1]
]

let solution = Solution()
let root = solution.construct(grid)

printQuadTree(root)

输出:

txt 复制代码
Leaf: 1

因为整块区域都是 1,所以整个树只有一个节点。

再试另一个比较复杂的:

txt 复制代码
grid = [
  [0,1],
  [1,0]
]

结果大概是:

txt 复制代码
Node
  Leaf: 0
  Leaf: 1
  Leaf: 1
  Leaf: 0

表示四个区域分别为 0、1、1、0。

时间复杂度

对于每一级,我们会对某个区域扫描一遍判断是否统一。

区域大小会依次为:

txt 复制代码
n^2 + 4 * (n/2)^2 + 16 * (n/4)^2 + ...

这个序列的和是:

O(n²)

因为所有区域总和面积就是 grid 面积。

所以:

  • 时间复杂度:O(n²)

非常合理。

空间复杂度

主要是递归栈和树结构本身。

  • 树的节点数量最多是:
    四叉树最多节点数 = O(n²)(最坏情况每个单元格都是一个叶子)
  • 递归深度为 log n

所以整体空间复杂度:

  • O(n²)

总结

这题的核心就是熟悉四叉树的定义,然后用递归自然地实现整棵树。

关键点回顾:

  1. 区域统一 → 叶子节点
  2. 区域不统一 → 四块继续递归
  3. 坐标 / size 控制是整题的重要细节
  4. 内部节点的 val 无影响,但要设置 isLeaf = false

掌握这个模式后,你会发现四叉树其实不难,它和你平时熟悉的"递归分治"套路非常一致,只是"从二叉树变成了四叉树"而已。

相关推荐
u***j3241 小时前
算法设计模式总结
算法·设计模式
vir022 小时前
交换瓶子(贪心)
数据结构·算法
G***66912 小时前
算法设计模式:贪心与动态规划
算法·设计模式·动态规划
墨染点香3 小时前
LeetCode 刷题【160. 相交链表】
算法·leetcode·链表
少睡点觉3 小时前
LeetCode 238. 除自身以外数组的乘积 问题分析+解析
java·算法·leetcode
YoungHong19923 小时前
面试经典150题[066]:分隔链表(LeetCode 86)
leetcode·链表·面试
大千AI助手3 小时前
多叉树:核心概念、算法实现与全领域应用
人工智能·算法·决策树·机器学习··多叉树·大千ai助手
一只老丸3 小时前
HOT100题打卡第38天——贪心算法
算法·贪心算法
普通网友3 小时前
高性能TCP服务器设计
开发语言·c++·算法