四叉树交集

标签: 分治

难度: Medium

二进制矩阵中的所有元素不是 0 就是 1

给你两个四叉树,quadTree1quadTree2。其中 quadTree1 表示一个 n * n 二进制矩阵,而 quadTree2 表示另一个 n * n 二进制矩阵。

请你返回一个表示 n * n 二进制矩阵的四叉树,它是 quadTree1quadTree2 所表示的两个二进制矩阵进行 按位逻辑或运算 的结果。

注意,当 isLeafFalse 时,你可以把 True 或者 False 赋值给节点,两种值都会被判题机制 接受

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

  • val:储存叶子结点所代表的区域的值。1 对应 True,0 对应 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:

输入:quadTree1 = [[0,1],[1,1],[1,1],[1,0],[1,0]]
, quadTree2 = [[0,1],[1,1],[0,1],[1,1],[1,0],null,null,null,null,[1,0],[1,0],[1,1],[1,1]]
输出:[[0,0],[1,1],[1,1],[1,1],[1,0]]
解释:quadTree1 和 quadTree2 如上所示。由四叉树所表示的二进制矩阵也已经给出。
如果我们对这两个矩阵进行按位逻辑或运算,则可以得到下面的二进制矩阵,由一个作为结果的四叉树表示。
注意,我们展示的二进制矩阵仅仅是为了更好地说明题意,你无需构造二进制矩阵来获得结果四叉树。

示例 2:

输入:quadTree1 = [[1,0]]
, quadTree2 = [[1,0]]
输出:[[1,0]]
解释:两个数所表示的矩阵大小都为 1*1,值全为 0 
结果矩阵大小为 1*1,值全为 0 。

示例 3:

输入:quadTree1 = [[0,0],[1,0],[1,0],[1,1],[1,1]]
, quadTree2 = [[0,0],[1,1],[1,1],[1,0],[1,1]]
输出:[[1,1]]

示例 4:

输入:quadTree1 = [[0,0],[1,1],[1,0],[1,1],[1,1]]
, quadTree2 = [[0,0],[1,1],[0,1],[1,1],[1,1],null,null,null,null,[1,1],[1,0],[1,0],[1,1]]
输出:[[0,0],[1,1],[0,1],[1,1],[1,1],null,null,null,null,[1,1],[1,0],[1,0],[1,1]]

示例 5:

输入:quadTree1 = [[0,1],[1,0],[0,1],[1,1],[1,0],null,null,null,null,[1,0],[1,0],[1,1],[1,1]]
, quadTree2 = [[0,1],[0,1],[1,0],[1,1],[1,0],[1,0],[1,0],[1,1],[1,1]]
输出:[[0,0],[0,1],[0,1],[1,1],[1,0],[1,0],[1,0],[1,1],[1,1],[1,0],[1,0],[1,1],[1,1]]

 

提示:

  • quadTree1quadTree2 都是符合题目要求的四叉树,每个都代表一个 n * n 的矩阵。
  • n == 2^x ,其中 0 <= x <= 9.

Submission

运行时间: 36 ms

内存: 17.1 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 intersect(self, quadTree1: 'Node', quadTree2: 'Node') -> 'Node':
        node = Node(val=None, isLeaf=False, topLeft=None, topRight=None, bottomLeft=None, bottomRight=None)
        if(quadTree1.isLeaf and quadTree2.isLeaf):
            node.val = quadTree1.val or quadTree2.val
            node.isLeaf = True
            return node
        if(quadTree1.isLeaf):
            if(quadTree1.val == 1):
                return quadTree1 
            else:
                return quadTree2
        if(quadTree2.isLeaf):
            if(quadTree2.val == 1):
                return quadTree2
            else:
                return quadTree1
        tl = self.intersect(quadTree1.topLeft, quadTree2.topLeft)
        tr = self.intersect(quadTree1.topRight, quadTree2.topRight)
        bl = self.intersect(quadTree1.bottomLeft, quadTree2.bottomLeft)
        br = self.intersect(quadTree1.bottomRight, quadTree2.bottomRight)
        if(tl.val == tr.val == bl.val == br.val != None and tl.isLeaf == tr.isLeaf == bl.isLeaf == br.isLeaf == True):
            node.val = tl.val 
            node.isLeaf = True 
            return node
        node.topLeft = tl
        node.topRight = tr
        node.bottomLeft = bl
        node.bottomRight = br
        return node

Explain

该题解采用递归方法对两个四叉树节点进行合并。若两节点都是叶子节点,则执行逻辑或操作。如果其中一个节点是叶子节点,则根据该叶子节点的值决定返回哪个节点。若两节点都不是叶子节点,则递归合并它们的子节点。最后,如果合并后的四个子节点都是叶子且值相同,则合并为一个叶子节点。

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

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

# 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 intersect(self, quadTree1: 'Node', quadTree2: 'Node') -> 'Node':
        # 创建新节点
        node = Node(val=None, isLeaf=False, topLeft=None, topRight=None, bottomLeft=None, bottomRight=None)
        # 若两节点均为叶子节点,直接计算或运算结果
        if quadTree1.isLeaf and quadTree2.isLeaf:
            node.val = quadTree1.val or quadTree2.val
            node.isLeaf = True
            return node
        # 若quadTree1为叶子节点
        if quadTree1.isLeaf:
            if quadTree1.val == 1:
                return quadTree1
            else:
                return quadTree2
        # 若quadTree2为叶子节点
        if quadTree2.isLeaf:
            if quadTree2.val == 1:
                return quadTree2
            else:
                return quadTree1
        # 递归合并子节点
        tl = self.intersect(quadTree1.topLeft, quadTree2.topLeft)
        tr = self.intersect(quadTree1.topRight, quadTree2.topRight)
        bl = self.intersect(quadTree1.bottomLeft, quadTree2.bottomLeft)
        br = self.intersect(quadTree1.bottomRight, quadTree2.bottomRight)
        # 如果四个子节点均为叶子且值相同,则合并为一个叶子节点
        if tl.val == tr.val == bl.val == br.val != None and tl.isLeaf == tr.isLeaf == bl.isLeaf == br.isLeaf == True:
            node.val = tl.val
            node.isLeaf = True
            return node
        # 分配合并后的子节点
        node.topLeft = tl
        node.topRight = tr
        node.bottomLeft = bl
        node.bottomRight = br
        return node

Explore

在四叉树的结构中,非叶子节点的值并不直接代表具体的数据,而是指示其子节点的存在。因此,对于非叶子节点,其节点值的检查并不是必要的。我们需要关注的是子节点的合并逻辑。当两个节点都是非叶子节点时,意味着它们都具有更详细的子结构需要进行合并,所以直接递归合并每个对应的子节点是逻辑上正确且必要的步骤。

当四个子节点在递归合并后都是叶子节点且值相同,将它们合并为一个叶子节点的逻辑一般情况下是正确的,因为这反映了这四个子区域的属性完全一致。这种合并可以大幅减少四叉树的复杂度和所需的空间。然而,需要注意的是,这种合并策略假设四叉树的构造和操作都围绕均匀性的简化。如果实际应用中四叉树的更新操作频繁,可能需要重新评估是否每次都进行合并,因为频繁的合并和拆分可能会影响性能。

在递归调用过程中创建大量的Node实例确实可能对性能产生显著影响,特别是在处理大规模数据或高分辨率的n*n矩阵时。每次递归调用都可能生成新的节点,这不仅增加了内存的使用,也可能导致更高的处理时间。优化方法包括使用内存池来管理Node实例,或者在算法中实现更高效的内存管理策略,如使用原地修改而非每次都创建新节点。

在Node类的构造函数中将val参数设置为None主要是为了初始化一个非叶子节点,在这种情况下节点的具体值由其子节点决定。这种设计在逻辑上符合非叶子节点的特性,即它们本身不直接持有数据值。然而,这种做法要求在使用节点值进行操作之前,必须确保节点是叶子节点。如果代码中存在对非叶子节点的val值进行操作的情况,而没有进行适当的检查,就可能导致错误。因此,合理的错误检查和代码健壮性是必要的。