左神算法笔记10:树形dp、Morris遍历

本文讲解了如何运用树形dp方法解决二叉树中涉及最大距离(如直径问题)和最大快乐值(员工参与决策)的问题。通过递归分析节点子树情况,合并左右子树信息,设计高效的递归函数,展示了在Morris遍历背景下的算法实现与应用.
摘要由CSDN通过智能技术生成

树形dp套路

使用前提:
如果题目求解目标是S规则,则求解流程可以定成以每个节点为头结点的子树在S规则下的每一个答案,并且最终答案一定在其中

套路讲解

树形dp套路第一步:
以某个节点x为头结点的子树中,分析答案有哪些可能性,并且这种分析是以x的左子树,x的右子树和x整棵树的角度来考虑可能性

树形dp套路第二步:
根据第一步的可能性分析,列出所需要的信息

树形dp套路第三步:
合并第二步的信息,对左树和右树提出同样的要求,并写出信息结构

树形dp套路第四步:
设计递归函数,递归函数是处理以x为头结点的情况下的答案
包括设计递归的basecase,默认直接得到左树和右树的所有信息,以及把所有可能性做整合,并且要返回第三步的信息结构

二叉树节点间的最大距离问题(二叉树的直径问题)

从二叉树的节点a出发,可以向上或者向下走,但沿途的节点只能经过一次,到达节点b时路径上的节点个数叫做a到b的距离,那么二叉树任意两个节点之间都有距离,求整棵树上的最大距离
力扣原题

分析

整棵树的最大距离分为两种情况

  1. 根节点x不参与
    此时求出左子树和右子树的最大距离
  2. 根节点x参与
    此时最大距离为左树高度+右树高度+1
  3. 三者中取最大值即为整棵树的最大距离

代码实现

	public static int maxDistance(Node head){
		return process(head).maxDistance;
	}
	public static class Info {
        int maxDistance;   //表示最大距离
        int height;  //表示树的高度

        public Info(int maxDistance, int height) {
            this.maxDistance = maxDistance;
            this.height = height;
        }
    }
    public static Info treeMaxDistance(Node root) {
        if (root == null) {
            return new Info(0, 0);
        }
        Info leftInfo = treeMaxDistance(root.left);
        Info rightInfo = treeMaxDistance(root.right);
        //info

        int p1 = leftInfo.maxDistance;
        int p2 = rightInfo.maxDistance;
        int p3 = leftInfo.height + rightInfo.height + 1;
        int maxDistance = Math.max(Math.max(p1, p2), p3);
        int height = Math.max(leftInfo.height, rightInfo.height) + 1;
        return new Info(maxDistance, height);
    }

派对的最大快乐值

在这里插入图片描述

分析

在这里插入图片描述

代码实现

package Test;
import java.util.List;
/**
 * @Author laimouren
 * @Date 2021/12/12 10:24
 */
public class MaxHappy {
    public static class Employee {
        public int happy;// 这名员工可以带来的欢乐值
        public List<Employee> nexts;// 这名员工有哪些直接下级
        public Employee(int happy, List<Employee> employeeList) {
            this.happy = happy;
            this.nexts = employeeList;
        }
    }
    public static class Info {
        public int laiMaxHappy; // 头结点来的时候最大快乐值
        public int buMaxHappy;  // 头结点不来时最大快乐值

        public Info(int lai, int bu) {
            laiMaxHappy = lai;
            buMaxHappy = bu;
        }
    }
    public static int maxHappy(Employee boss) {
        if (boss == null) {
            return 0;
        }
        Info all = process(boss);
        return Math.max(all.laiMaxHappy, all.buMaxHappy);
    }


    public static Info process(Employee x) {
        // 以基层员工作为终止条件
        if (x.nexts.isEmpty()) {
            return new Info(x.happy, 0);
        }
        //x来的情况下,整棵树的最大快乐值
        int lai = x.happy;
        //x不来的情况下,整棵树的最大快乐值
        int bu = 0;
        for (Employee next : x.nexts) {
            Info nextInfo = process(next);
            //当x来的时候,下级员工只能不来
            lai += nextInfo.buMaxHappy;
            //当x不来的时候,下级员工可以来也可以不来,最大快乐值取两者中最大的
            bu += Math.max(nextInfo.laiMaxHappy, nextInfo.buMaxHappy);
        }
        return new Info(lai, bu);
    }
}

Morris遍历

一种遍历二叉树的方式,并且时间复杂度为O(N),额外空间复杂度为O(1)
通过利用原树中大量空闲指针的方式,达到节省空间的目的
在这里插入图片描述

分析

在这里插入图片描述

代码实现

public static void morris(Node head) {
   if (head == null) {
      return;
   }
   Node cur = head;
   Node mostRight = null;
   while (cur != null) {
   	  //mostRight是cur左孩子
      mostRight = cur.left;
      //有左树的情况,即情况2
      if (mostRight != null) {
         while (mostRight.right != null && mostRight.right != cur) {
            mostRight = mostRight.right;
         }
         //此时mostRight是cur左子树上的最右节点,如果为空说明是第一次来到cur
         if (mostRight.right == null) {
            mostRight.right = cur;
            cur = cur.left;
            continue;
         //不为空说明不是第一次来cur
         } else {
            mostRight.right = null;  
         }   
      }
      //情况1
      cur = cur.right;
   }
}

在这里插入图片描述

改先序遍历

public static void preMorris(Node head) {
   if (head == null) {
      return;
   }
   Node cur = head;
   Node mostRight = null;
   while (cur != null) {
      mostRight = cur.left;
      //有左树
      if (mostRight != null) {
         while (mostRight.right != null && mostRight.right != cur) {
            mostRight = mostRight.right;
         }
         //此时mostRight是cur左子树上的最右节点,如果为空说明是第一次来到cur
         if (mostRight.right == null) {
         	System.out.println(cur.value);
            mostRight.right = cur;
            cur = cur.left;
            continue;
         //不为空说明不是第一次来cur
         } else {
            mostRight.right = null;  
         }   
      }
      //没有左子树的情况
      else{
      	System.out.println(cur.value);
      }
      cur = cur.right;
   }
}

改中序遍历

public static void inMorris(Node head) {
   if (head == null) {
      return;
   }
   Node cur = head;
   Node mostRight = null;
   while (cur != null) {
   	  //mostRight是cur左孩子
      mostRight = cur.left;
      if (mostRight != null) {
         while (mostRight.right != null && mostRight.right != cur) {
            mostRight = mostRight.right;
         }
         //此时mostRight是cur左子树上的最右节点,如果为空说明是第一次来到cur
         if (mostRight.right == null) {
            mostRight.right = cur;
            cur = cur.left;
            continue;
         //不为空说明不是第一次来cur
         } else {
            mostRight.right = null;  
         }   
      }
      System.out.println(cur.value);
      cur = cur.right;
   }
}

改后序遍历

public static void morrisPos(Node head) {
        if(head == null){
            return;
        }
        Node cur = head;
        Node mostRight = null;
        while (cur != null){
            mostRight = cur.left;
            if(mostRight != null){
                while (mostRight.right !=null && mostRight.right != cur){
                    mostRight = mostRight.right;
                }
                if(mostRight.right == null){
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                }else {
                    mostRight.right = null;
                    printEdge(cur.left);
                }
            }
            cur = cur.right;
        }
        printEdge(head);
        System.out.println();
    }
    //打印逆序完的二叉树,最后再将其还原
    public static void printEdge(Node node){
        Node tail =reverseEdge(node);
        Node cur = tail;
        while (cur != null ){
            System.out.print(cur.value+" ");
            cur =cur.right;
        }
        reverseEdge(tail);
    }
    //逆序一棵树
    public static Node reverseEdge(Node node){
        Node pre = null;
        Node next = null;
        while (node != null){
            next = node.right;
            node.right = pre;
            pre = node;
            node = next;
        }
        return pre;
    }

判断一棵树是否是搜索二叉树

public static boolean isBST(Node head) {
   if (head == null) {
      return true;
   }
   Node cur = head;
   Node mostRight = null;
   //之前的值
   int preValue = Integer.MIN_VALUE;
   while (cur != null) {
   	  //mostRight是cur左孩子
      mostRight = cur.left;
      //有左树的情况,即情况2
      if (mostRight != null) {
         while (mostRight.right != null && mostRight.right != cur) {
            mostRight = mostRight.right;
         }
         //此时mostRight是cur左子树上的最右节点,如果为空说明是第一次来到cur
         if (mostRight.right == null) {
            mostRight.right = cur;
            cur = cur.left;
            continue;
         //不为空说明不是第一次来cur
         } else {
            mostRight.right = null;  
         }   
      }
      //中序遍历时每个元素都是严格递增的,所以如果有<=的情况出现,
      //说明不是搜索二叉树
      if(cur.value <= preValue){
      	return false;
      }
      preValue = cur.value;
      cur = cur.right;
   }
   return true;
}

当二叉树问题需要整合当前节点和左子树和右子树的值才能解决问题时,用二叉树的递归套路
当二叉树问题只需遍历即可找出答案时,用Morris是最优解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值