LeetCode_BinaryTree_968. Binary Tree Cameras 监控二叉树【递归,树形DP】【C++/java】【困难】

目录

一,题目描述

英文描述

中文描述

示例与说明

二,解题思路

算法

状态划分 

分情况讨论

三,AC代码

C++

Java

四,解题过程

第一博

第二搏

第三博


一,题目描述

英文描述

You are given the root of a binary tree. We install cameras on the tree nodes where each camera at a node can monitor its parent, itself, and its immediate children.

Return the minimum number of cameras needed to monitor all nodes of the tree.

中文描述

给定一个二叉树,我们在树的节点上安装摄像头。

节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。

计算监控树的所有节点所需的最小摄像头数量。

示例与说明

提示:

  1. 给定树的节点数的范围是 [1, 1000]
  2. 每个节点的值都是 0。

来源:力扣(LeetCode)
链接:力扣
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

二,解题思路

参考@笨猪爆破组【「手画图解」从递归优化到树形DP | 监控二叉树】,文章介绍的比较详细,感兴趣的可以戳链接。

算法

状态划分 

对于一个非空节点可以分为三种状态:

  1. 安装摄像头withCam(被自己监控);
  2. 不安装摄像头(被父节点监控watchByFat);
  3. 不安装摄像头(被子节点监控watchBySon);

若节点为空,需要注意:

  • 空节点不可能放置摄像头(此情况可以将返回值设置为INT_MAX,从而避免错误解被纳入结果考虑范围);
  • 空节点被父节点监控或是被子节点监控都返回0即可;

分情况讨论

1,当前节点安装摄像头:(子节点可装可不装,都会被当前节点监控)

  • minNum = min(minNum, left["withCam"] + right["watchByFat"]);
  • minNum = min(minNum, left["watchByFat"] + right["withCam"]);
  • minNum = min(minNum, left["watchByFat"] + right["watchByFat"]);
  • minNum = min(minNum, left["withCam"] + right["withCam"]);(此情况可以不用考虑,因为当前节点左右子节点都安装摄像头的策略,基本不会是最优策略)

2,当前节点不安装摄像头:(子节点不可能被当前节点监控。只能被自己监控,或者被他们的子节点监控)

(当前节点被父节点监控。子节点可装可不装)

  • minNum = min(minNum, left["withCam"] + right["withCam"]);
  • minNum = min(minNum, left["withCam"] + right["watchBySon"]);
  • minNum = min(minNum, left["watchBySon"] + right["withCam"]);
  • minNum = min(minNum, left["watchBySon"] + right["watchBySon"]);

(当前节点被子节点监控。子节点至少有一个摄像头)

  • minNum = min(minNum, left["withCam"] + right["withCam"]);
  • minNum = min(minNum, left["withCam"] + right["watchBySon"]);
  • minNum = min(minNum, left["watchBySon"] + right["withCam"]);

至此便考虑到了所有可能成为解的情况,并将计算的结果一步步返回递归的上一层,从而重复利用。

三,AC代码

C++

class Solution {
public:
    int minCameraCover(TreeNode* root) {
        unordered_map<string, int> ans = getMin(root);
        int minNum = 99999999;
        minNum = min(minNum, ans["withCam"]);
        // minNum = min(minNum, ans["watchByFat"]); // !!!根节点怎么可能被父节点监控
        minNum = min(minNum, ans["watchBySon"]);
        return minNum;
    }
    unordered_map<string, int> getMin(TreeNode* root) {
        unordered_map<string, int> ans;
        if (root == nullptr) {
            ans["withCam"] = 99999999;// 空节点不可能有摄像头,所以这里返回一个足够大的值,使得这种情况不可能被纳入最优解考虑范围
            ans["watchByFat"] = 0;// 空节点不需要被监控
            ans["watchBySon"] = 0;// 空节点不可能被子节点监控
            return ans;// !!!必须在这里返回
        }
        unordered_map<string, int> left = getMin(root->left);
        unordered_map<string, int> right = getMin(root->right);
        
        // 当前节点安装摄像头
        int minNum = 99999999;
        minNum = min(minNum, left["withCam"] + right["watchByFat"]);
        minNum = min(minNum, left["watchByFat"] + right["withCam"]);
        minNum = min(minNum, left["watchByFat"] + right["watchByFat"]);
        int withCam = 1 + minNum;

        // 当前节点不安装摄像头
        // 被父节点监控
        minNum = 99999999;
        minNum = min(minNum, left["withCam"] + right["withCam"]);
        minNum = min(minNum, left["withCam"] + right["watchBySon"]);
        minNum = min(minNum, left["watchBySon"] + right["withCam"]);
        minNum = min(minNum, left["watchBySon"] + right["watchBySon"]);
        int watchByFat = minNum;

        // 被子节点监控(子节点至少有一个摄像头)
        minNum = 99999999;
        minNum = min(minNum, left["withCam"] + right["withCam"]);
        minNum = min(minNum, left["withCam"] + right["watchBySon"]);
        minNum = min(minNum, left["watchBySon"] + right["withCam"]);
        int watchBySon = minNum;
        
        ans["withCam"] = withCam;
        ans["watchByFat"] = watchByFat;
        ans["watchBySon"] = watchBySon;
        return ans;
    }
    
};

Java

class Solution {
    public int minCameraCover(TreeNode root) {
        Reult ans = getMin(root);
        return Math.min(ans.withCam, ans.watchBySon);
    }
    public Reult getMin(TreeNode root) {
        Reult ans = new Reult();
        if (root == null) {
            ans.withCam = 99999999;
            ans.watchByFat = 0;
            ans.watchBySon = 0;
            return ans;
        }
        Reult left = getMin(root.left);
        Reult right = getMin(root.right);
        // 在该节点放置摄像头
        ans.withCam = 99999999;
        ans.withCam = Math.min(ans.withCam, left.withCam + right.watchByFat);
        ans.withCam = Math.min(ans.withCam, left.watchByFat + right.withCam);
        ans.withCam = Math.min(ans.withCam, left.watchByFat + right.watchByFat);
        ans.withCam++;

        // 该节点不放置摄像头(被父节点监控)
        ans.watchByFat = 99999999;
        ans.watchByFat = Math.min(ans.watchByFat, left.withCam + right.watchBySon);
        ans.watchByFat = Math.min(ans.watchByFat, left.watchBySon + right.withCam);
        ans.watchByFat = Math.min(ans.watchByFat, left.withCam + right.withCam);
        ans.watchByFat = Math.min(ans.watchByFat, left.watchBySon + right.watchBySon);

        // 该节点不放置摄像头(被子节点监控)
        ans.watchBySon = 99999999;
        ans.watchBySon = Math.min(ans.watchBySon, left.withCam + right.watchBySon);
        ans.watchBySon = Math.min(ans.watchBySon, left.watchBySon + right.withCam);
        ans.watchBySon = Math.min(ans.watchBySon, left.withCam + right.withCam);

        return ans;
    }
}
class Reult {
    int withCam;
    int watchByFat;
    int watchBySon;
}

四,解题过程

第一博

这种一般直接想到用递归,只要归纳全部的情况就能得到正确答案,虽然看上去效率很低,但是现实现出来再说

class Solution {
    public int minCameraCover(TreeNode root) {
        return Math.min(getAns(root, 0), getAns(root, 1));

    }
    // flag表示当前节点是否安装摄像头
    public int getAns(TreeNode root, int flag) {
        if (root == null) return 0;
        int minNum = 99999999;
        if (flag == 1) {
            minNum = Math.min(minNum, getAns(root.left, 1) + getAns(root.right, 1));
            minNum = Math.min(minNum, getAns(root.left, 1) + getAns(root.right, 0));
            minNum = Math.min(minNum, getAns(root.left, 0) + getAns(root.right, 1));
            minNum = Math.min(minNum, getAns(root.left, 0) + getAns(root.right, 0));
        } else {
            if (root.left != null && root.right != null) {
                minNum = Math.min(minNum, getAns(root.left, 1) + getAns(root.right, 1));
                minNum = Math.min(minNum, getAns(root.left, 1) + getAns(root.right, 0));
                minNum = Math.min(minNum, getAns(root.left, 0) + getAns(root.right, 1));
            } else if (root.left == null && root.right == null) {
                return flag;
            } else if (root.left == null) {
                minNum = Math.min(minNum, getAns(root.left, 0) + getAns(root.right, 1));
            } else if (root.right == null) {
                minNum = Math.min(minNum, getAns(root.left, 1) + getAns(root.right, 0));
            }
        }
        System.out.println(minNum + flag);
        return minNum + flag;
    }
}

显然上面的方法没有考虑到父节点的状态,因此没有包含所有的状态,最后只过了121/171

第二搏

参考了大佬的解题思路,发现是自己考虑的情况不够全面 ,比如只考虑当前节点放不放摄像头,未考虑父节点或子节点对当前节点监控状态的影响,这样就无法完全保证覆盖关系,无法判断连续两个节点不放置摄像头的情况。

因此需要引入当前节点是否被监视的参数。

class Solution {
    public int minCameraCover(TreeNode root) {
        return Math.min(getNum(root, true, true), getNum(root, false, false));
    }
    // hasCame: 该节点是否有摄像头,isWatched: 是否被自己或父节点监控
    public int getNum(TreeNode root,boolean hasCame, boolean isWatched) {
        if (root == null) {
            if (hasCame) return 99999999;
            else return 0;
        }
        int minNum = 99999999;
        // 该节点有摄像头(子结点可装可不装,舍弃子结点全部安装的情况,因为该情况不可能作为最终解)
        if (hasCame) {
            minNum = Math.min(minNum, getNum(root.left, true, true) + getNum(root.right, false, true) + 1);
            minNum = Math.min(minNum, getNum(root.left, false, true) + getNum(root.right, true, true) + 1);
            minNum = Math.min(minNum, getNum(root.left, false, true) + getNum(root.right, false, true) + 1);
        } 
        // 该节点无摄像头
        else {
            // 父节点有摄像头,被父节点监控(子结点可装可不装)
            if (isWatched) {
                minNum = Math.min(minNum, getNum(root.left, true, true) + getNum(root.right, true, true));
                minNum = Math.min(minNum, getNum(root.left, false, false) + getNum(root.right, true, true));
                minNum = Math.min(minNum, getNum(root.left, true, true) + getNum(root.right, false, false));
                minNum = Math.min(minNum, getNum(root.left, false, false) + getNum(root.right, false, false));
            } 
            // 父节点无摄像头,被子结点监控(至少有一个子结点要装摄像头)
            else {
                minNum = Math.min(minNum, getNum(root.left, true, true) + getNum(root.right, false, false));
                minNum = Math.min(minNum, getNum(root.left, false, false) + getNum(root.right, true, true));
                minNum = Math.min(minNum, getNum(root.left, true, true) + getNum(root.right, true, true));
            }
        }
        return minNum;
    }
}

显然,后面的测试用例都是超时了。

第三博

可以从上面的算法中看出,为保证正确性,多次对同一颗子树进行求解,导致计算重复,如果能够在一次递归中将各种情况的计算结果一次性返回,并且重复利用就可以提高效率。这便是树形DP

解题过程用的是map,这样看起来更直观一点,但是开销比较大,想提高效率可以用数组代替 

猜你喜欢

转载自blog.csdn.net/qq_41528502/article/details/121002726