DAY41:贪心算法(十)监控二叉树

这篇具有很好参考价值的文章主要介绍了DAY41:贪心算法(十)监控二叉树。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

968.监控二叉树

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

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

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

DAY41:贪心算法(十)监控二叉树,刷题记录,贪心算法,算法,c++,leetcode,数据结构
输入:[0,0,null,0,0]
输出:1
解释:如图所示,一台摄像头足以监控所有节点。

DAY41:贪心算法(十)监控二叉树,刷题记录,贪心算法,算法,c++,leetcode,数据结构
输入:[0,0,null,0,null,0,null,null,0]
输出:2
解释:需要至少两个摄像头来监视树的所有节点。 上图显示了摄像头放置的有效位置之一。

提示:

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

思路

本题一个重要思路是,二叉树中一定是叶子节点数量大于根节点数量的。叶子节点和根节点比起来,数量呈现指数级的扩大,因此我们想要用最少的摄像头,就要在叶子节点上优先想如何节约摄像头

在叶子节点的情况下,我们要优先在叶子节点的父节点上放摄像头,然后一层一层往上推,往上推的逻辑是每隔2个节点放一个摄像头

遍历顺序

因为我们从叶子节点开始,从下往上处理二叉树,所以一定是后序遍历。遍历的同时,我们需要记录每个节点的状态,从而根据左右孩子的状态去确定父节点的状态

这里就涉及到一个状态转移的问题,但是本题的状态转移和动规的状态转移不同,本题只是涉及到一个每个节点状态的判断,并不是要得到最优状态。本题中节点有三种状态:

  • 0:无摄像头,无覆盖
  • 1:有摄像头
  • 2:无摄像头,有覆盖(在摄像头范围内)

我们在后序遍历的时候,可以通过状态转移,来判断左右孩子是某状态的时候,父节点应该是什么状态

空节点处理

本题的目的,就是尽可能让叶子节点的父节点去放摄像头,这样摄像头数目才能最少。为了让叶子节点的父节点能放摄像头,空节点应该是有覆盖的状态。

也就是说,遇到空节点应该向上返回2,这样才能让叶子节点父节点有摄像头。

情况列举
  • 当前节点左右孩子都无摄像头有覆盖,也就是状态2,此时当前节点必然是状态0

DAY41:贪心算法(十)监控二叉树,刷题记录,贪心算法,算法,c++,leetcode,数据结构

  • 当前节点左右孩子至少有一个无覆盖,此时当前节点一定要装一个摄像头,也就是状态1,否则就不能全覆盖了

DAY41:贪心算法(十)监控二叉树,刷题记录,贪心算法,算法,c++,leetcode,数据结构

  • 左右孩子至少有一个有摄像头,那么当前节点一定是2

DAY41:贪心算法(十)监控二叉树,刷题记录,贪心算法,算法,c++,leetcode,数据结构

  • 最后一种情况是基于第一种情况,第一种情况的当前节点无覆盖,需要等待父节点去把他覆盖。但是第一种情况的节点,如果是根节点,就没有父节点可以装摄像头!

    此时根节点本身必须装一个摄像头,否则根节点就是无覆盖的状态。

本题所有可能的情况就限于这四种。主要是分析出来前三种,第四种是遍历到最后对根节点进行单独的判断。

最开始的写法

class Solution {
public:
    //摄像头个数
    int result=0;
    //后序遍历,有返回值,返回当前节点状态
    int travelsal(TreeNode* root){
        //后序终止条件
        if(root==nullptr) return 2;//空节点返回状态2

        //左右中 后序遍历
        int left = travelsal(root->left);
        int right = travelsal(root->right);

        //判断情况,第一种,左右都有cover无摄像头 左右都是状态2
        if(left==2&&right==2){
            return 0;//无cover 返回0让父节点加摄像头
        }
        //第二种,左右有一个是0
        if(left==0||right==0){
            result++;
            return 1;
        }
        //第三种,左右有一个有摄像头
        if(left==1||right==1){
            return 2;
        }
        //逻辑不会走到这里,只是为了有返回值
        return -1;
    }
    int minCameraCover(TreeNode* root) {
        travelsal(root);
        cout<<travelsal(root)<<endl;
        //第四种,root无cover,但是没有父节点了
        if(travelsal(root)==0){
            result++;
        }
        return result;
    }
};
debug测试:travelsal的输出多了1

存在逻辑问题,travelsal的输出多了1

DAY41:贪心算法(十)监控二叉树,刷题记录,贪心算法,算法,c++,leetcode,数据结构
但是实际上,后序遍历的逻辑是正确的,但是主函数写法出了问题

报错的主函数:

int minCameraCover(TreeNode* root) {
     travelsal(root);
     cout<<travelsal(root)<<endl;
     //第四种,root无cover,但是没有父节点了
     if(travelsal(root)==0)
     //if(state==0)
        result++;
     return result;
    }

注意这里的问题是,函数重写一次就会重复运行一次,上面这段主函数代码,相当于travelsal运行了三次

第一次是travelsal(root);,第二次是cout<<travelsal(root)<<endl;,第三次是if(travelsal(root)==0)函数只要出现就会运行一次,而result是一个全局变量,每次运行都会在之前运行的基础上+1.

因此,这里才会出现全局变量是3,但是函数实际逻辑正确的情况,就是因为函数写了三次所以运行了三遍。(这是一个很基础的逻辑问题,函数重新写一次就会运行一次,即使是在cout里面重写,也会运行第二遍,希望下次不要犯这种离谱错误了)

修改版

  • 把主函数写法改掉就可以了
class Solution {
public:
    //摄像头个数
    int result=0;
    //后序遍历,有返回值,返回当前节点状态
    int travelsal(TreeNode* root){
        //后序终止条件
        if(root==nullptr) return 2;//空节点返回状态2

        //左右中 后序遍历
        int left = travelsal(root->left);
        int right = travelsal(root->right);

        //判断情况,第一种,左右都有cover无摄像头 左右都是状态2
        if(left==2&&right==2){
            return 0;//无cover 返回0让父节点加摄像头
        }
        //第二种,左右有一个是0
        if(left==0||right==0){
            result++;
            return 1;
        }
        //第三种,左右有一个有摄像头
        if(left==1||right==1){
            return 2;
        }
        //逻辑不会走到这里,只是为了有返回值
        return -1;


    }
    int minCameraCover(TreeNode* root) {
        int state = travelsal(root);
        cout<<state<<endl;
        //第四种,root无cover,但是没有父节点了
        //if(travelsal(root)==0)
        if(state==0)
            result++;
            return result;
    }
};

或者主函数可以写成

  • 比较简洁的写法,return也可以直接返回表达式,不容易出错
int minCameraCover(TreeNode* root) {
     int state = travelsal(root);
     return result+=(state==0?1:0);
}

二叉树注意点

  • 前后序的区别主要在处理节点的顺序,处理当前节点需要下面节点的状态就会用后序,但是所有遍历的终止条件都是遇到空节点返回(也就是触底了才会return),前序也是遇到空节点才开始return
  • 最后return -1 是因为逻辑不会走到这里但是函数需要一个返回值,return什么都可以

时间复杂度

  • 时间复杂度: O(n),需要遍历二叉树上的每个节点
  • 空间复杂度: O(n),本题的空间复杂度在于递归的返回值return输入的二叉树本身是不算空间复杂度的,但是每遍历到一个节点都会return 0/1/2状态值,也就是说每次递归的返回值都占了一个int的内存。所以空间复杂度是return的数值,一共n个数值所以是O(n)

总结

本题的难点首先是要想到贪心的思路,要想得到最少摄像头,就必须在叶子节点的父节点上面放摄像头

然后就是遍历和状态推导,首先要想到二叉树每个节点,都可以归结为三种状态

第二,要想到二叉树每个叶子节点的状态去推当前节点的状态,一共可以归纳为四种情况;然后通过遍历在二叉树上进行状态推导,后序遍历的同时接收下面叶子节点的状态,从而判断出当前的状态和需不需要放置摄像头。

难点在于情况的分析,不要想复杂,所有的情况就是3+1种(最后一种是根节点单独考虑)文章来源地址https://www.toymoban.com/news/detail-539153.html

到了这里,关于DAY41:贪心算法(十)监控二叉树的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • leetcode刷题记录:二叉树02(思路篇)

    参考labuladong的算法小抄:https://labuladong.online/algo/data-structure/binary-tree-part1/ 复习二叉树纲领篇,二叉树解题的思维模式分两类: 1、是否可以通过遍历一遍二叉树得到答案?如果可以,用一个 traverse 函数配合外部变量来实现,这叫「遍历」的思维模式。 2、是否可以定义一个

    2024年02月19日
    浏览(28)
  • 贪心算法part6 | ● 738.单调递增的数字 ● 968.监控二叉树

    代码随想录算法训练营第一天 | 题目、题目 738.单调递增的数字 局部最优:前一个数比当前数小,前一个数位减一,当前数置为9 题目要求小于等于N的最大单调递增的整数,那么拿一个两位的数字来举例。 例如:98,一旦出现strNum[i - 1] strNum[i]的情况(非单调递增),首先想

    2024年02月10日
    浏览(29)
  • 算法刷题Day 15 二叉树的层序遍历+翻转二叉树+对称二叉树

    层序遍历二叉树需要借助到队列 递归方法 迭代方法 就是简单的用上前序遍历迭代方法实现,不用想的太复杂 递归方法 迭代方法 这里使用了队列来存放两个要比较的结点。也可以使用栈,方法是类似的。

    2024年02月12日
    浏览(31)
  • 算法刷题Day 17 平衡二叉树+二叉树的所有路径+左叶子之和

    计算左右两棵子树的高度,如果有一个高度是-1(有一棵子树不平衡),直接返回-1,否则计算高度差,判断是否不平衡 使用 回溯 的方法,每次处理一个节点之前要把它push进table里,处理完之后又要把它pop出来 处理一个节点时,要判断它是否是左节点(需要父节点,可以通

    2024年02月15日
    浏览(31)
  • 算法刷题Day 37 单调递增的数字+监听二叉树

    两个可能经常要用到的函数 字符串转数字: to_string() 数字转字符串: stoi() 利用树后续遍历,同时加上状态转移的方法,非常值得反复学习

    2024年02月13日
    浏览(26)
  • 算法刷题Day 20 最大二叉树+合并二叉树+二叉搜索树中的搜索+验证二叉搜索树

    递归 递归 迭代 递归 迭代 遇到两个坑的地方: 递归的时候,不能光验证左子树的根节点小于当前根节点,右子树的根节点大于但当前根节点,别忘了左子树上的每一个节点都要求比根节点要小,右子树上的每一个节点都要求比根节点大。 根节点跟左(右)子树中的某一个节

    2024年02月16日
    浏览(31)
  • 算法刷题Day 16 二叉树的最大深度+N叉树的最大深度+二叉树的最小深度+完全二叉树的节点个数

    递归法 迭代法 使用层序的方法,相对比较好理解 递归法 迭代法 跟二叉树的迭代差不多意思。 要想到是后序遍历 递归法 先计算左右两棵子树的节点数,再加和,用后序遍历的方法 迭代法 迭代法用层序遍历来处理 适用于完全二叉树的优化 完全二叉树优化方法没自己想出来

    2024年02月17日
    浏览(32)
  • 算法训练day41|动态规划 part03(LeetCode343. 整数拆分、96.不同的二叉搜索树)

    题目链接🔥🔥 给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。 示例 1: 输入: 2 输出: 1 解释: 2 = 1 + 1, 1 × 1 = 1。 示例 2: 输入: 10 输出: 36 解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。 说明: 你可以假设 n 不小于 2 且不大于

    2024年02月10日
    浏览(32)
  • 算法训练day15Leetcode102二叉树层序遍历226翻转二叉树101对称二叉树

    https://www.bilibili.com/video/BV1ue4y1Y7Mf/?vd_source=8272bd48fee17396a4a1746c256ab0ae 层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。这种遍历的方式和我们之前讲过的都不太一样。 需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先

    2024年01月18日
    浏览(40)
  • 算法刷题Day14 二叉树的前序、中序、后序遍历(递归、迭代、统一迭代方法)

    二叉树的定义 递归 迭代 普通的遍历(包括前序,中序和后续)迭代方法都需要借助到栈 统一迭代 统一迭代使用标记法,在栈中要处理的结点压入空指针 递归 迭代 中序遍历的迭代方法稍微特殊一点 中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问

    2024年02月15日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包