LeetCode 968. 监控二叉树 动态规划和贪心算法详解 Java

968. 监控二叉树

题目来源

968. 监控二叉树

题目分析

给定一个二叉树,要求在节点上安装摄像头,以使得每个节点都能被监控到。摄像头可以监控其自身、父节点及其直接子节点。需要计算出监控整棵树所需的最少摄像头数量。

题目难度

  • 难度:困难

题目标签

  • 标签:动态规划, 树, 贪心

题目限制

  • 1 <= 给定的二叉树的节点个数 <= 100

解题思路

思路一:DFS + 动态规划

  1. 状态定义

    • 每个节点有三种状态:
      1. 装了摄像头:当前节点安装了摄像头。
      2. 父节点被监控:当前节点未安装摄像头,但其父节点有摄像头覆盖。
      3. 子节点被监控:当前节点未安装摄像头,但其子节点有摄像头覆盖。
  2. 递归方程

    • 使用 DFS 递归遍历整棵树,对于每个节点计算其三种状态下的最小摄像头数量。
    • 基本情况处理:
      当 root 为空时,返回 {Integer.MAX_VALUE / 2, 0, 0}。这里使用 Integer.MAX_VALUE / 2 是为了避免整数溢出,同时确保这个值足够大,不会被选中。然而,这里存在问题,即如果根节点本身就是空的,那么最终返回的最小摄像头数量将是 Integer.MAX_VALUE / 2,这可能不是预期的结果。通常情况下,我们应该返回一个有意义的值,比如 {0, 0, 0},表示空树不需要摄像头。
    • 递归计算:
      递归地计算左右子树的结果,并基于这些结果来决定当前节点的最佳状态。
      choose 表示在当前节点放置摄像头的情况。这里使用 min 函数是因为我们需要选择左子树和右子树中摄像头数量最少的情况。对于每个子树,我们考虑三种情况:不放置摄像头、放置摄像头、被监控。然后加上当前节点放置摄像头的数量 1。
      byFather 表示当前节点被父节点监控的情况。这里我们只需要考虑左子树和右子树不放置摄像头或者被监控的情况,因为我们假设当前节点已经被父节点监控了。
      byChildren 表示当前节点被子节点监控的情况。这里我们考虑了三种情况:
      • 左子树不放置摄像头,右子树被监控。
      • 右子树不放置摄像头,左子树被监控。
      • 左子树和右子树都放置摄像头。
        我们需要选择这三种情况中摄像头数量最少的一种。
  3. 最终结果

    • 对根节点的状态进行计算,取最优解即为最少摄像头数量。

思路二:贪心策略

  1. 贪心策略:通过递归遍历树,并在每个节点上根据子节点的状态来决定是否需要放置摄像头

    • 从叶节点的父节点开始尽量安装摄像头,使得尽可能少的摄像头能覆盖尽量多的节点。
    • 每个节点的状态分为:无覆盖有摄像头有覆盖但无摄像头
  2. 递归实现

    • 使用递归判断每个节点的状态,根据子节点的状态决定是否安装摄像头。
    • 状态表示:
      0 表示当前节点没有被覆盖。
      1 表示当前节点有摄像头。
      2 表示当前节点被覆盖,但没有摄像头。
    • 边界条件:将null节点状态置为2,如果置为0则叶子节点全部都要安装摄像头,贪心算法不符,如果全部置为1,空节点也不能安装摄像头。
    • 递归逻辑:
      如果左右子节点中有任何一个节点的状态为 0(未被覆盖),则在当前节点放置摄像头,并返回 1 表示当前节点有摄像头。
      如果左右子节点中有任何一个节点的状态为 1(有摄像头),则当前节点被覆盖,返回 2。
      如果左右子节点的状态均为 2(被覆盖,但无摄像头),则当前节点未被覆盖,返回 0。

核心算法步骤

动态规划实现:
  1. DFS 递归

    • 对每个节点进行 DFS 递归,计算三种状态下的最小摄像头数量。
  2. 状态转移

    • 左右子树的状态决定当前节点的最优状态。
  3. 返回结果

    • 根节点状态中,取安装摄像头和不安装摄像头的最优解。
贪心策略实现:
  1. 状态定义

    • 定义节点的三种状态:无覆盖有摄像头有覆盖但无摄像头
  2. 递归遍历

    • 从子节点开始递归遍历,通过状态判断是否需要安装摄像头。

代码实现

以下是解决监控二叉树问题的 Java 代码:

/**
 * 968. 监控二叉树
 * @param root 二叉树
 * @return 最小摄像头数量
 */
public int minCameraCover(TreeNode<Integer> root) {
    // 1. 动态规划实现
    int[] res = minCameraCoverDfs(root);
    return Math.min(res[0], res[2]);

    // 2. 贪心策略实现
//    if(minCameraCoverGreedy(root) == 0) {
//        ans++; // 如果根节点无覆盖,需要在根节点安装摄像头
//    }
//    return ans;
}

public int[] minCameraCoverDfs(TreeNode<Integer> root) {
    if (root == null) {
        return new int[]{Integer.MAX_VALUE / 2, 0, 0};
    }

    int[] left = minCameraCoverDfs(root.left);
    int[] right = minCameraCoverDfs(root.right);

    int choose = Math.min(left[0], Math.min(left[1], left[2])) +
                 Math.min(right[0], Math.min(right[1], right[2])) + 1;
    int byFather = Math.min(left[0], left[2]) + Math.min(right[0], right[2]);
    int byChildren = Math.min(Math.min(left[0] + right[2], right[0] + left[2]), left[0] + right[0]);

    return new int[]{choose, byFather, byChildren};
}

public int minCameraCoverGreedy(TreeNode<Integer> root) {
    // 0. 无覆盖 1. 有摄像头 2. 有覆盖但无摄像头
    if (root == null) {
        return 2;
    }

    int left = minCameraCoverGreedy(root.left);
    int right = minCameraCoverGreedy(root.right);

    if (left == 0 || right == 0) {
        ans++;
        return 1; // 安装摄像头
    }

    if (left == 1 || right == 1) {
        return 2; // 当前节点有覆盖,但无摄像头
    }

    return 0; // 当前节点无覆盖
}

private int ans = 0;

代码解读

  • minCameraCoverDfs(TreeNode<Integer> root):实现了动态规划的递归,返回三种状态下的最小摄像头数量。
  • minCameraCoverGreedy(TreeNode<Integer> root):实现了贪心策略的递归判断,每个节点根据左右子树的状态决定是否安装摄像头。

性能分析

1. 动态规划解法
  • 时间复杂度O(N),其中 N 是二叉树的节点数。在该解法中,每个节点被遍历一次,通过 DFS 递归地计算其状态。由于每个节点都只被访问一次,整个算法的时间复杂度为线性。

  • 空间复杂度O(N),主要来源于递归调用栈的空间消耗。在最坏情况下(例如二叉树呈现为一条链状结构),递归深度可能达到 N,因此空间复杂度为 O(N)。此外,还需要 O(N) 的空间来存储每个节点对应的三种状态的结果。

2. 贪心策略解法
  • 时间复杂度O(N),与动态规划解法相同,贪心策略同样基于 DFS 递归遍历整棵树,每个节点仅访问一次。因此,时间复杂度也是线性 O(N)

  • 空间复杂度O(N),贪心策略的递归调用栈深度同样可能达到 N,特别是在树的结构非常偏向单边的情况下。因此,空间复杂度也为 O(N)。相较于动态规划,贪心策略没有额外的存储需求,仅依赖递归调用栈的空间。

测试用例

以下是一些测试用例,用于验证代码的正确性:

TreeNode<Integer> root1 = new TreeNode<>(0);
root1.left = new TreeNode<>(0);
root1.left.left = new TreeNode<>(0);
root1.left.right = new TreeNode<>(0);
System.out.println(minCameraCover(root1)); // 输出: 1

TreeNode<Integer> root2 = new TreeNode<>(0);
root2.left = new TreeNode<>(0);
root2.left.left = new TreeNode<>(0);
root2.left.left.left = new TreeNode<>(0);
System.out.println(minCameraCover(root2)); // 输出: 2

效果

动态规划

动态规划

贪心

贪心

总结

本题通过 DFS 和动态规划,以及贪心策略两种思路,计算了监控二叉树所需的最小摄像头数量。动态规划的解法适合大多数树结构问题,贪心策略则提供了一种更加直接的思路。


  • 23
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值