[二叉树] 监控二叉树(困难)

前言

今天的题目是一道将贪心和动态规划融合在二叉树的题目。

题目

监控二叉树

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

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

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

题目拆解

这道题目的核心关键点在于如何使用最少的监控,覆盖整个树。在最优化的处理办法上,通常使用动态规划和贪心来处理,今天也采用这两种方式进行解答。

动态规划

使用动态规划的思路主要是注意状态转移,在传统的单线路动态规划上,状态转移还比较好实现,而到了二叉树中,状态转移变得稍微有些繁琐,我们来一步步分析。

状态定义

动态规划的思路其实是站在整体的范围考量,以整颗树的覆盖情况作为一个基本状态,然后将状态转移到父节点上。那么,一棵树有几种状态呢?直观来向,有点难度,我们不妨站在一个节点的立场上,考虑下这个节点需要依赖子树的什么状态吧。

  • 左右子树都被覆盖了,但是左右子树的根节点没有监控。
  • 左右子树都被覆盖了,并且左右子树至少有一颗的根节点有监控。
  • 左右子树的子节点都被覆盖了,但是左右子树的根节点至少有一个没有被覆盖。

1、在第一种状态下,左右子树全部被监控覆盖,但是因为左右子树的根节点都没有监控,所以当前节点要考虑要不要加监控了(看父节点能不能加监控)。
2、在第二种状态下,左右子树全部被覆盖,并且有一棵树的根节点已经有监控存在,可以覆盖到当前节点,所以,本节点可以不加。
3、在第三种状态下,左右子树的子节点全部被覆盖,但是至少有一课子树的根节点没有被覆盖,那么当前节点必须加监控,否则会有节点覆盖不到。

所以,整理一下,一个节点是否要加监控有三种选择,必须加,需要自己加或者父节点加,可以不加,我们将这三种状态映射到树的本身,整理三种状态:

1、状态a: 一棵树全部被监控覆盖,并且根节点有监控。(父节点可以不加)

2、状态b:一棵树全部被监控覆盖,根节点可以有监控也可以没有。(父节加不加要看情况)

3、状态c:一棵树除了根节点以外全部被监控覆盖,根节点可以被覆盖也可以不被覆盖。(父节点必须加)

画三张图来说明一下

状态a
image.png
状态b
image.png
状态c
image.png

以上是三种状态下树的展示形态,因为每一个节点的监控添加与否都取决于子树的这三种状态的对应监控数量,所以,我们就为每一个节点维护这三个状态的存储,然后在递归的时候,把状态向父节点转移。

接下来,我们用Ca,Cb,Cc来表示在不同状态下,需要的监控数量,通过示例图可以看出,
Ca>=Cb>=Cc
我们最终实际需要的,或者说我们想获得的监控数量,是在状态b下的监控数量,状态a冗余了一个监控,状态c可能存在缺失根节点覆盖。

所以,动态规划的第一步,我们完成了,用一个int[]维护三种状态对应的监控数量。

接下来,来进行状态转移。

状态转移

状态定义好了,状态转移其实比较容易。
遍历每一个节点的时候,分别计算Ca,Cb,Cc的值,假设子节点的状态数组为int[] left,右节点的状态数组为int[] right。那么有。

  • Ca = left[2]+right[2] + 1; // 不管子节点根节点是否有监控,直接把当前节点插一个监控。
  • Cb = Math.min(Ca,Math.min(left[0]+right[1],left[1]+right[0])) // 从左右子树分别取一个Ca和一个Cb,最后取最小值,保证根节点被覆盖,然后再和Ca取最小值。
  • Cc = Math.min(Cb,left[1]+right[2])

这样我们就完成了状态转移,有了状态定义和状态转移,我们的代码也就出来了。

动态规划代码

class Solution {


    public int minCameraCover(TreeNode root) {
        int[] res = dfsOne(root);
        return res[1];


    }

    int[] dfsOne(TreeNode node) {
        if (node == null) return new int[]{Integer.MAX_VALUE / 2, 0, 0};

        int[] array = new int[3];
        int[] leftArray = dfsOne(node.left);
        int[] rightArray = dfsOne(node.right);

        int a = leftArray[2] + rightArray[2] + 1;
        int b = Math.min(a, Math.min(leftArray[1] + rightArray[0], leftArray[0] + rightArray[1]));
        int c = Math.min(b, leftArray[1] + rightArray[1]);

        array[0] = a;
        array[1] = b;
        array[2] = c;
        return array;


    }
}

**************************************分割线**************************************

贪心

贪心算法的核心,其实就一个字,贪。

如果说还有一个字,那就是,懒。

相比于动态规划是站在整棵树的立场上触发,贪心就完全是站在节点自身的角度出发。
除非必须加监控,否则绝对不加监控,这就贪心算法的核心,在梳理逻辑的时候也要把这句话牢记在心头。

那我们需要做的就是看看遍历到一个节点时这个节点的心路历程。

  • 左右子树的根节点有监控了,那太好了,啥也不用管,告诉老板被覆盖了,不用管我了。
  • 如果子节点被监控覆盖了,但是左右子树的根节点没有监控,这时候咋办呢,自己先不管,把这事先告诉老板,让老板看看咋办。
  • 如果子节点没有被监控覆盖,那么当前节点必须加,不加没人能覆盖子节点了,告诉老板我加监控了,邀功请赏。

这三种状态,与动态规划不同的是,贪心算法中,当前节点只向上级汇报自己的状态,不需要管整棵树的状态。

我们也将该节点的状态定义一下。

  • 状态1,当前节点已经被覆盖
  • 状态2,当前节点没被覆盖
  • 状态3,当前节点有监控

这个思路有了,判断的标准也出来了,我们来看下贪心的代码

贪心代码

class Solution {


    public int minCameraCover(TreeNode root) {


        int resTwo = dfsTwo(root);
        if (resTwo == 2) {
            totalCount++;
        }

        return totalCount;
    }

    private int totalCount = 0;

    int dfsTwo(TreeNode node) {
        // 状态1,当前节点已经被覆盖
        // 状态2,当前节点没被覆盖
        // 状态3,当前节点有摄像头

        if (node == null) {
            // 空节点默认被覆盖
            return 1;
        }

        int left = dfsTwo(node.left);
        int right = dfsTwo(node.right);

        // 自己的两个子节点有一方没有被覆盖,那么自己不加不行了,在当前节点加一个监控
        if (left == 2 || right == 2) {
            totalCount++;
            return 3;
        }

        // 两个子节点有一个加了监控,那当前节点已经被覆盖,没必要加监控了.
        if (left == 3 || right == 3) {
            return 1;
        }
        // 到这里只剩下一种情况 left == 1 right == 1
        // 说明当前节点没有被覆盖,但是子节点已经被覆盖了,自己先不管,把这个结果告诉老板就得了.

        return 2;

    }

总结

今天这道题是一道困难题,所以整理了一下官方题解和其他各路大神的题解,总结了这两种思路来做,也表示一下对困难题目的respect。

实际上,可以看出来,这道题目的代码不多,去掉注释就没几行了,但是思考问题的切入点和思路确实有点复杂,是需要认真琢磨一下的。

此外,动态规划和贪心两种思路上,其实本质都是一种状态的向上传递,只不过动态规划立足于描述整棵树,贪心描述节点自身,动态规划省却了很多状态的判断,贪心没有那么多数据的冗余。

喜欢哪个就用哪个吧~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容