二叉树灯饰

标签: 深度优先搜索 动态规划 二叉树

难度: Medium

「力扣嘉年华」的中心广场放置了一个巨型的二叉树形状的装饰树。每个节点上均有一盏灯和三个开关。节点值为 0 表示灯处于「关闭」状态,节点值为 1 表示灯处于「开启」状态。每个节点上的三个开关各自功能如下:

  • 开关 1:切换当前节点的灯的状态;
  • 开关 2:切换 以当前节点为根 的子树中,所有节点上的灯的状态;
  • 开关 3:切换 当前节点及其左右子节点(若存在的话) 上的灯的状态;

给定该装饰的初始状态 root,请返回最少需要操作多少次开关,可以关闭所有节点的灯。

示例 1:

输入:root = [1,1,0,null,null,null,1]
输出:2
解释:以下是最佳的方案之一,如图所示

示例 2:

输入:root = [1,1,1,1,null,null,1]
输出:1
解释:以下是最佳的方案,如图所示

示例 3:

输入:root = [0,null,0]
输出:0
解释:无需操作开关,当前所有节点上的灯均已关闭

提示:

  • 1 <= 节点个数 <= 10^5
  • 0 <= Node.val <= 1

Submission

运行时间: 305 ms

内存: 31.7 MB

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def closeLampInTree(self, root: TreeNode) -> int:
        # 1:全亮 2:全灭 3:当前灯亮,其余全灭 4:当前灯灭,其余全亮
        def dfs(c):
            if not c:
                return 0,0,0,0
            l1,l2,l3,l4 = dfs(c.left)
            r1,r2,r3,r4 = dfs(c.right)
            if c.val:
                t1 = min(l1 + r1,2 + l2 + r2,2 + l3 + r3,2 + l4 + r4)
                t2 = min(1 + l2 + r2,l1 + r1 + 1,l3 + r3 + 1,l4 + r4 + 3)
                t3 = min(l1 + r1 + 2,l2 + r2,l3 + r3 + 2,l4 + r4 + 2)
                t4 = min(l1 + r1 + 1,l2 + r2 + 1,l3 + r3 + 3,l4 + r4 + 1)
            else:
                t1 = min(l1 + r1 + 1,1 + l2 + r2,3 + l3 + r3,1 + l4 + r4)
                t2 = min(l2 + r2,l1 + r1 + 2,l3 + r3 + 2,l4 + r4 + 2)
                t3 = min(l1 + r1 + 1,l2 + r2 + 1,l3 + r3 + 1,l4 + r4 + 3)
                t4 = min(l1 + r1,l2 + r2 + 2,l3 + r3 + 2,l4 + r4 + 2)
            return t1,t2,t3,t4
        
        ans = dfs(root)
        return ans[1]

Explain

The solution employs a dynamic programming approach using depth-first search (DFS) to determine the minimum number of switch operations needed to turn off all the lights in a binary tree. At each node, four states are computed, representing the minimum operations required for different configurations: 1) all lights on, 2) all lights off, 3) only the current node light on, and 4) only the current node light off. These states are computed based on the node's current state (light on or off) and the minimal operations required for its children's states. The recursive DFS function calculates the optimal switch operations for each state by combining results from left and right children and considering all possible switch actions at the current node.

时间复杂度: O(n)

空间复杂度: O(h) where h is the height of the tree, worst-case O(n)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def closeLampInTree(self, root: TreeNode) -> int:
        # Recursive DFS to compute the minimal switch operations for each node's states
        def dfs(c):
            if not c:
                return 0,0,0,0 # Base case: if current node is None, return zero operations for all states
            # Recurse on left and right children
            l1,l2,l3,l4 = dfs(c.left)
            r1,r2,r3,r4 = dfs(c.right)
            # Calculate min operations for each state based on current node's light status
            if c.val: # If current node's light is on
                t1 = min(l1 + r1, 2 + l2 + r2, 2 + l3 + r3, 2 + l4 + r4) # all lights on
                t2 = min(1 + l2 + r2, l1 + r1 + 1, l3 + r3 + 1, l4 + r4 + 3) # all lights off
                t3 = min(l1 + r1 + 2, l2 + r2, l3 + r3 + 2, l4 + r4 + 2) # current on, others off
                t4 = min(l1 + r1 + 1, l2 + r2 + 1, l3 + r3 + 3, l4 + r4 + 1) # current off, others on
            else: # If current node's light is off
                t1 = min(l1 + r1 + 1, 1 + l2 + r2, 3 + l3 + r3, 1 + l4 + r4) # all lights on
                t2 = min(l2 + r2, l1 + r1 + 2, l3 + r3 + 2, l4 + r4 + 2) # all lights off
                t3 = min(l1 + r1 + 1, l2 + r2 + 1, l3 + r3 + 1, l4 + r4 + 3) # current on, others off
                t4 = min(l1 + r1, l2 + r2 + 2, l3 + r3 + 2, l4 + r4 + 2) # current off, others on
            return t1, t2, t3, t4 # Return calculated states
        # Start DFS from the root
        ans = dfs(root)
        return ans[1] # Return the minimum operations to turn all lights off

Explore

在这个题解中,每个节点都计算了四种不同的状态,这些状态反映了不同的灯光配置及其所需的最小操作次数。这四种状态分别为:1) 所有灯都亮着;2) 所有灯都熄灭;3) 仅当前节点的灯亮着,其他都熄灭;4) 当前节点的灯熄灭,其他都亮着。第一种状态用于计算将当前节点以及其所有子节点的灯全部点亮所需的最小操作数。第二种状态用于计算将所有灯熄灭的最小操作数。第三种状态专注于只有当前节点灯亮,其余节点灯都熄灭的情形,这在一些情况下可以最小化操作次数。第四种状态则是当前节点灯熄灭,其他节点灯亮的配置,这也是一种特殊的灯光状态。

在这个题解中,`1`代表进行一次开关操作,无论是将灯从开启状态切换到关闭状态,还是从关闭状态切换到开启状态。例如,在表达式`1 + l2 + r2`中,`1`表示对当前节点进行一次操作(如果当前灯是亮的,就关闭它;如果是灭的,就打开它),而`l2`和`r2`分别表示左、右子树已经全部熄灭的最小操作数。同理,`l1 + r1 + 1`中的`1`也表示对当前节点进行一次操作,`l1`和`r1`表示左右子树处于全亮状态的最小操作数。这些操作确保了在转换状态时,能够从一个状态准确地转换到另一个所需状态,同时保证操作次数最小。

深度优先搜索(DFS)是一种递归遍历树的方法,它从根节点开始,递归地访问每个节点的左子树和右子树。在这个题解中,DFS被用来计算每个节点在四种不同状态下的最小操作数。通过递归调用,DFS确保了从每个叶子节点开始到根节点的每一步中,都将左子树和右子树的所有可能状态考虑在内。这样,每次递归返回时,都会结合当前节点的状态和其子节点的状态,计算出当前节点的最优状态。这种自底向上的计算方式确保了每个节点在计算时都有完整的子树信息,从而保证了所有可能的情况都被充分考虑。