青训营X豆包MarsCode 技术训练营第一课| 豆包MarsCode AI 刷题

88 阅读4分钟

伴学笔记-第一篇-学习方法与心得

题目解析:选择豆包MarsCode AI 刷题(代码练习)题库中的任意题目进行解析,如思路、图解、代码详解

问题

image-20241125203451392

题目描述

image-20241125203536089

题目解析

题目输入按照层次遍历结果,1代表有该节点,0代表无节点。题目要求尽量少地供应暖炉,所谓暖炉就是覆盖某节点自身、父节点、子节点,求一个算法,使得覆盖二叉树的暖炉数量最少

解题思路

贪心思想、确立遍历顺序

由题目可以知道,如果将暖炉放在根节点、叶子节点,那么就没法覆盖到其父节点、子节点,所以是浪费的,所以不要放在根节点及叶子节点。这是一种贪心的思想,确立贪心思想后,我们可以选择遍历顺序,是从根节点向下遍历(前序、层次、中序),还是从叶子节点向上遍历(后序),我们发现,根节点只有一个,节省效果有限,然而叶子节点是根节点的指数级的,节省的效果更大,所以确立从叶子节点向上遍历(后序遍历)。

这个思考方法也符合贪心思想的内涵。一个大问题可以分解成若干的子问题,找到每一个子问题的最优解,按照顺序叠加起来(前提是可以叠加子问题),那么就是这个大问题的最优解。

从另一个角度看,就是从局部最优推出整体最优,这里的局部最优就是照顾量级叶子节点的父节点安装暖炉,使得叶子节点能够节省(由于叶子节点最多,所以是数量级的,所以有说服力,可以这样分解子问题),使得整体最优:全部暖炉安装数量最小。

保障每隔两个节点放一个暖炉

要解决这个问题,除了确立遍历顺序后,还要保障怎么每隔两个节点放一个暖炉。我们需要总结一下状态的变化(状态转移)。

每一个节点可能有三种状态:

  1. 该节点没覆盖
  2. 该节点有暖炉
  3. 该节点被暖炉覆盖(但没有暖炉)

不妨就用3个数字来表示他们(0,1,2)

初始化

我们约定了遍历顺序和节点状态,那么需要考虑初始化过程,就是怎么使得第一个暖炉放置在叶子节点的父节点,因为叶子节点是没覆盖的,所以要使得判断条件通用,不妨假设空节点(叶子节点的子节点)是有覆盖的,那么自然而然就放到叶子节点的父节点了。

逻辑处理

接下来还得处理节点间的情况

  1. 左右节点都有覆盖->那么父节点无需放置暖炉

  2. 左右节点至少一个无覆盖->那么父节点需要放置暖炉

  3. 左右节点至少一个有暖炉->那么父节点无需放置暖炉

    1. 左右节点都有暖炉->那么父节点无需暖炉
    2. 左右节点有一个有暖炉,有一个有覆盖->那么父节点无需暖炉
    3. 左右节点有一个有暖炉,有一个无覆盖->其实我们在分解这个问题的时候,需要保障一个原则就是无需回头考虑,这里只需关注父节点是否要安装暖炉,无覆盖的那个节点应该在其下一级去判断,故,父节点无需暖炉。
  4. 根节点没有覆盖->结束特别判断,显然需要放置暖炉

代码与注释

python

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right
​
def buildBinaryTree(levelOrder):
    if not levelOrder:
        return None
    root = TreeNode(levelOrder[0])
    queue = [root]
    i = 1
    while i < len(levelOrder):
        node = queue.pop(0)
        if levelOrder[i] != 0:
            node.left = TreeNode(levelOrder[i])
            queue.append(node.left)
        i += 1
        if i < len(levelOrder) and levelOrder[i] != 0:
            node.right = TreeNode(levelOrder[i])
            queue.append(node.right)
        i += 1
    return root
​
def solution(nodes):
    # 构建二叉树的根节点
    root = buildBinaryTree(nodes)
    result = 0
​
    # 深度优先遍历
    def traversal(cur):
        nonlocal result
        if cur is None:
            return 2  # 如果当前节点为空,则返回 2 表示已经暖和
​
        left = traversal(cur.left)
        right = traversal(cur.right)
​
        if left == 2 and right == 2:
            # 左右节点都已经暖和,当前节点不需要暖炉
            return 0
​
        if left == 0 or right == 0:
            # 如果左右子节点中有一个需要暖炉
            result += 1
            return 1  # 当前节点安装暖炉
​
        if left == 1 or right == 1:
            # 左右节点中有一个已经安装了暖炉
            return 2  # 当前节点已经暖和
​
        return -1
​
    # 如果根节点需要暖炉
    if traversal(root) == 0:
        result += 1
​
    return result
​
if __name__ == "__main__":
    #  You can add more test cases here
    print(solution([1, 1, 0, 1, 1]) == 1 )
    print(solution([1,0,1,1,0,1,0,1,0,1,0,0,1,1]) == 3 )
    print(solution([1,1,0,0,1,1,0,0,1,0,1,1,0,0,1]) == 3 )