关于树形dp问题的解决

解决套路

实际上就是设计一个递归函数,该递归函数一定要包含 basecase即让函数趋于终止的条件

处理节点 node 时,我们默认直接能够获得左子树和右子树的相关信息,然后将所有可能性进行整合。

往往是通过 node 的左子树,node 的右子树,包含 node 的整棵树来分析可能性,从而根据题目列出需要的信息

对节点 node 是这样要求的,同样对于右子树和左子树也要提出相同的要求,进行递归

案例展示

二叉树节点类

class TreeNode {
  int val = 0;
  TreeNode left = null;
  TreeNode right = null;
  public TreeNode(int val) {
    this.val = val;
  }
}

一、二叉树的最大深度

在这里插入图片描述

basecase:

如果 root 为 null,即为空节点,那么返回的高度为 0

递归设计:

默认获取到了 root 节点的左子树高度和右子树高度

节点 root 的高度一定等于其左子树的高度和右子树的高度的最大值,然后在此基础上加上 1,表示到了 root 节点层,高度增加了 1

public class Solution {
    public int maxDepth (TreeNode root) {
        if(root == null) {
            return 0;//basecase
        } 
        return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
    }
}

二、判断是不是平衡二叉树

平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

在案例一的基础上,我们可以选择直接使用 IsBalanced_Solution() 方法来求得节点 root 是否是平衡二叉树,通过 maxDepth() 方法来求得节点 root 的左子树和右子树的高度。如果高度差的绝对值超过 1,那么直接歇菜,返回 false;如果没有超过 1,那么就再次使用 IsBalanced_Solution() 方法来验证 root.left 和 root.right 是否是平衡二叉树

这样的方法可行,但是会出现很多的重复计算树的高度的行为,所以可以选择通过一个类来记录信息,信息包括这棵树是否为平衡二叉树,以及该树的高度是多少

basecase:

如果节点 root 为 null,即空节点,那么它一定是一颗平衡二叉树,并且高度为 0

递归设计:

默认已经获取到了左子树和右子树的高度,也知晓左右子树是否都是平衡二叉树

那么以 root 为头结点的树成为平衡二叉树有三个条件:

①左子树是平衡树 ②右子树是平衡树 ③左右子树的高度差的绝对值不超过 1

求得该子树是否为平衡二叉树后,需要求得该子树的高度,其高度就是左右子树高度的最大值加一,为 root 节点的父节点提供信息(如果此时的 root 节点并不是题目提供的树的头结点的话)

public class Solution {
    class Demo2 {
        public boolean isBLC;//该子树是否是平衡二叉树
        public int height;//该子树的高度
        public Demo2(boolean isBLC,int height) {
            this.isBLC = isBLC;
            this.height = height;
        }
    }
    public boolean IsBalanced_Solution(TreeNode root) {
        if(root == null) {
            return true;
        }
        return func(root).isBLC;
    }
    public Demo2 func(TreeNode root) {
        if(root == null) {
            return new Demo2(true,0);//basecase
        }
        Demo2 demo1 = func(root.left);//获取左子树信息
        Demo2 demo2 = func(root.right);//获取右子树信息

        //判断当前子树是否为平衡二叉树的三个条件
        boolean isBLC = demo1.isBLC && demo2.isBLC
                && (Math.abs(demo1.height-demo2.height) <= 1);

        //求得当前子树的高度
        int height = Math.max(demo1.height,demo2.height) + 1;
        return new Demo2(isBLC,height);
    }
}

三、判断是不是二叉搜索树

二叉搜索树满足每个节点的左子树上的所有节点均小于当前节点且右子树上的所有节点均大于当前节点。

根据二叉搜索树的这个特点,如果我们对该二叉树进行中序遍历,那么遍历得到的结果一定是升序的,所以我们可以通过中序遍历来判断该树是否为二叉搜索树

当然也可以使用树形dp套路来解决,通过一个类去记录某一棵树是否为二叉搜索树,这棵树的最大值以及最小值

basecase:

如果 root 节点为 null,即空节点,那就返回 null,表示没有该子树

递归设计:

默认已经获取到了左右子树是否为二叉搜索树信息,以及左右子树分别的最大值和最小值

根据搜索二叉树的特点,对于当前子树的头结点 root 来说,其左子树的最大值一定比 root 节点的值小,其右子树的最小值一定比 root 节点的值大

所以判断当前树是否为平衡二叉树的条件:

① 如果左子树存在,左子树一定是二叉搜索树,并且左子树的最大值一定比 root 节点的值小,否则歇菜

② 如果右子树存在,右子树一定是二叉搜索树,并且右子树的最小值一定比 root 节点的值大,否则歇菜

最后还要根据左子树、右子树的最大最小值以及 root 的值来更新当前树的最大值和最小值

public class Solution1 {
    public class returnType {
        public boolean isBST;//当前树是否是二叉搜索树
        public int max;//树的最大值
        public int min;//数的最小值
        public returnType(boolean isBST,int ma,int mi) {
            this.isBST = isBST;
            this.max = ma;
            this.min = mi;
        }
    }
    public boolean isValidBST (TreeNode root) {
        return func(root).isBST;
    }
    public returnType func(TreeNode root) {
        if(root == null) {
            return null;//该子树啥也没有
        }
        //获取到左右子树的结果
        returnType left = func(root.left);
        returnType right = func(root.right);
        //更新最大值和最小值
        int min = root.val;
        int max = root.val;
        if(left != null) {
            //左子树是有节点的
            min = Math.min(min,left.min);
            max = Math.max(max,left.max);
        }
        if(right != null) {
            //右子树是有节点的
            min = Math.min(min,right.min);
            max = Math.max(max,right.max);
        }
        boolean isBST = false;
        //成为二叉搜索树的条件
        if((left != null?(left.isBST && left.max < root.val) : true ) &&
                (right != null?(right.isBST && right.min > root.val) : true)) {
            isBST = true;
        }
        return new returnType(isBST,max,min);
    }
}

四、判断是否是满二叉树

满二叉树就是除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树

在这里插入图片描述

那么根据满二叉树的性质,我们可以发现规律:满二叉树的节点数量等于 2树的高度 -1

我们使用一个类来记录当前树的高度以及当前树的节点个数

basecase:

如果 root 为 null,即 root 为空节点,那么树的高度为 0,树的节点个数自然也为 0

递归设计:

默认知晓 root 节点的左子树的高度和节点个数,右子树的高度和节点个数

那么当前以 root 为头结点的树的高度自然就是其左右子树高度的最大值加一,树的节点个数自然就是左右子树的节点个数之和加一

public class Solution {
    class Demo {
        public int height;//子树高度
        public int count;//子树节点个数
        public Demo(int height,int count) {
            this.height = height;
            this.count = count;
        }
    }
    public boolean isFull(TreeNode root) {
        if(root == null) {
            return true;
        }
        Demo demo = func(root);
        //判断是否为满二叉树
        return 1 << demo.height - 1 == demo.count;
    }
    public Demo func(TreeNode root) {
        if (root == null) {
            return new Demo(0,0);
        }
        Demo demo1 = func(root.left);
        Demo demo2 = func(root.right);
        int height = Math.max(demo1.height,demo2.height) + 1;
        int count = demo1.count + demo2.count + 1;
        return new Demo(height,count);
    }
}

五、二叉树节点间的最大距离

从二叉树的某个节点 X 触发,可以向上走,也可以像下走,但是途经的节点不可重复经过,到达另一个节点 Y 时路径上的节点个数被认为是节点 X 到节点 Y 的距离(包含节点 X、Y),那么二叉树任何两个节点之间都会有距离,求整个树上的最大距离

在这里插入图片描述

如上图所示,该树的最大距离就是 7

通过 Info 这个类来记录信息,包含当前树的最大距离以及该树的高度

basecase:

如果 root 为 null,即为空节点,那么该树的最大距离和树的高度自然都为 0

递归设计:

默认知道 root 节点的左子树的最大距离以及高度,以及右子树的最大距离以及高度

如果 root 节点参与到最大距离的路径中了,所能够获得的最大距离就应该是左右子树高度之和再加一。

如果 root 节点没有参与到最大距离的路径中,那么获得的最大距离一定在左子树或者右子树中产生。

三者取最大即为该树的最大距离

public class Solution {
    class Info {
        public int distance;//该树能够获得的最大距离
        public int height;//该树的高度
        public Info (int distance,int height) {
            this.distance = distance;
            this.height = height;
        }
    }
    public int getDistance (TreeNode root) {
        return func(root).distance;
    }
    public Info func(TreeNode root) {
        if (root == null) {
            return new Info(0,0);
        }
        Info left = func(root.left);
        Info right = func(root.right);
        int num1 = left.distance;//左子树的最大距离(X不参与)
        int num2 = right.distance;//右子树的最大距离(X不参与)
        int num3 = left.height + right.height + 1;//X参与得到的最大距离
        int distance = Math.max(num1,Math.max(num2,num3));//三者取最大
        int height = Math.max(left.height, right.height)+1;//该树的高度
        return new Info(distance,height);
    }
}

六、派对的最大快乐值

现有员工信息可以通过一个类来定义

class Employee {
    public int happy;//该员工的快乐值
    List<Employee> subordinates;//该员工的直接下属们
}

公司的每个员工都符合 Employee 类的描述,所以可以将公司的人员结构看成是一颗多叉树,头结点自然就是大 boss,除了老板外,所有员工都有自己的唯一上级,除了基层员工(叶子节点)外,都有属于自己的一个或多个直接下属,基层员工的 subordinates 列表为null

现在需要办一个派对,员工可以来也可以不来,但是有一个硬性规定,员工甲来了,那么甲的直接下属就一定不能来。派对的快乐值就是参加派对的员工的快乐值的累加

求如何让派对的快乐值最大?返回最大值

通过 HappyInfo 这个类来记录员工 x 去派对所获得的最大快乐值以及不去派对所获得的最大快乐值

basecase:

如果员工 x 是基层员工,那么他是没有直系下属的,他去了,最大快乐值就是他自己的快乐值,不去就没有快乐值

递归设计:

默认已经知晓员工 x 所有直系下属去或者不去派对所能够获得的最大快乐值

如果员工 x 去了派对,那么他的直系下属一定去不了,我们就将他直系下属的去不了派对所获得的最大快乐值进行累加,再加上员工 x 的快乐值,从而得到员工 x 去派对所能够获得的最大快乐值

如果员工 x 不去派对,那么他的直系下属可以去,可以不去,两者取较大值进行累加操作,得到员工 x 不去派对所能够获得的最大快乐值

public class Solution {
    class HappyInfo {
        public int go;//去派对获得的最大快乐值
        public int notToGo;//不去派对获得的最大快乐值
        public HappyInfo (int go,int notToGo) {
            this.go = go;
            this.notToGo = notToGo;
        }
    }
    public int Party(Employee x) {
        HappyInfo happyInfo = getHappiness(x);//x 是大boss
        //取大 boss 去或者不去所获得的快乐值的较大值
        return Math.max(happyInfo.go,happyInfo.notToGo);
    }
    public HappyInfo getHappiness (Employee x) {
        if (x.subordinates == null) {
            return new HappyInfo(x.happy,0);//基层员工
        }
        int go = x.happy;//x 员工去派对得到的最大快乐
        int notToGo = 0;//x 员工不去派对得到的最大快乐
        
        //遍历一下 x 的所有直系下属
        for (Employee e:x.subordinates) {
            HappyInfo happyInfo = getHappiness(e);
            go += happyInfo.notToGo;
            //x 员工去了,直系下属一定不去
            notToGo += Math.max(happyInfo.go,happyInfo.notToGo);
            //x 没去,直系下属去或者不去取较大值
        }
        return new HappyInfo(go,notToGo);
    }
}
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

富春山居_ZYY(已黑化)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值