[C++算法] - 树形dp套路

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

https://www.cnblogs.com/mhpp/p/6628548.html 这其中是一些其他的例子,抽空可以看看。

 

目录

1. 树形dp套路

题目一  :二叉树节点间的最大距离问题

题目二 :派对的最大快乐值  

题目三 :最大搜索二叉子树

题目四:是否是平衡二叉树

题目五:二叉树 最近公共祖先(后序遍历)

题目六: 二叉树的最小深度(后序遍历)


1. 树形dp套路

【站在 左树也能要信息,右树也能要信息,考虑当前头的角度】

树形dp套路第一步:

以某个节点X为头节点的子树中,分析答案有哪些可能性,并且这种分析是以X的左子树、X的右子树和X整棵树的角度来考虑可能性的

树形dp套路第二步:

根据第一步的可能性分析,列出所有需要的信息

树形dp套路第三步:

合并第二步的信息,对左树和右树提出同样的要求,并写出信息结构

树形dp套路第四步:

设计递归函数,递归函数是处理以X为头节点的情况下的答案。

包括设计递归的basecase,默认直接得到左树和右树的所有信息,以及把可能性做整合,并且要返回第三步的信息结构这四个小步骤

 

题目一  :二叉树节点间的最大距离问题

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

 

1. 以X的左子树、X的右子树和X整棵树的角度来考虑可能性的

1) 和x有关,那就是左子树上最长的 到x 然后 到右子树

2) 和x无关

    A. 只与x的左子树有关 [ 如下图 ]

    B. 只与x的右子树有关

2. 根据第一步的可能性分析,列出所有需要的信息

左远到右远

左树上的最大距离,左远对应的左树的高度。

右树上的最大距离,右远对应的右树的高度。

3.合并第二步的信息,对左树和右树提出同样的要求,并写出信息结构【求并集】

如左树要最小,右树要最大,那么信息结构就是两者都要求。

4.设计递归函数,递归函数是处理以X为头节点的情况下的答案。

包括设计递归的basecase,默认直接得到左树和右树的所有信息,以及把可能性做整合,并且要返回第三步的信息结构这四个小步骤

public class Code02_MaxDistanceInTree {

	public static class Node {
		public int value;
		public Node left;
		public Node right;

		public Node(int data) {
			this.value = data;
		}
	}
	
	// 主函数,得到最大距离,输入一个头结点
	public static int maxDistance(Node head) {
		return process(head).maxDistance;
	}
	
	public static class Info{
		public int maxDistance;
		public int height;
		public Info(int dis, int h) {
			maxDistance = dis;
			height  = h;
		}
	}
	
	
	public static Info process(Node x) {//通过proess函数得到info
        // 1. basecase
		if(x == null) {
			return new Info(0,0);//构造新的info返回
		}
        // 2. 默认直接得到左树和右树的所有信息
		Info leftInfo = process(x.left);// 用了黑盒
		Info rightInfo = process(x.right);
		// info 拆解黑盒,拆出该函数的意义 ,这里是得到info【maxDistance,height】
        // 3. 可能性做整合 拆解黑盒
		int p1 = leftInfo.maxDistance;//情况一
		int p2 = rightInfo.maxDistance;//情况二
		int p3 = leftInfo.height + 1 + rightInfo.height;//情况三
		int maxDistance = Math.max(p3, Math.max(p1, p2));
		int height = Math.max(leftInfo.height, rightInfo.height) + 1 ;
        // 4. 返回第三步的信息结构
		return new Info(maxDistance, height);
	}
}


题目二 :派对的最大快乐值  

员工信息的定义如下:

class Employee {    

              public int happy; // 这名员工可以带来的快乐值    

              List<Employee> subordinates; // 这名员工有哪些直接下级 }

公司的每个员工都符合 Employee 类的描述。整个公司的人员结构可以看作是一棵标准的、 没有环的多叉树。树的头节点是公司唯一的老板。除老板之外的每个员工都有唯一的直接上级。 叶节点是没有任何下属的基层员工(subordinates列表为空),除基层员工外,每个员工都有一个或多个直接下级。 这个公司现在要办party,你可以决定哪些员工来,哪些员工不来。但是要遵循如下规则。

1.如果某个员工来了,那么这个员工的所有直接下级都不能来

2.派对的整体快乐值是所有到场员工快乐值的累加

3.你的目标是让派对的整体快乐值尽量大

给定一棵多叉树的头节点boss,请返回派对的最大快乐值。

 

首先从X开始分析,x整棵树可能性

	public static class Employee { 
		public int happy; // 这名员工可以带来的快乐值 
	    public List<Employee> nexts; // 这名员工有哪些直接下级 
	}
	
	
	public static int maxHappy(Employee boss) {
		Info headInfo = process(boss);
		return Math.max(headInfo.laiMaxHappy, headInfo.buMaxHappy);
	}
	
	// 得到一个员工,来or不来,分别对应的最大happy!!!!
	public static class Info{
		public int laiMaxHappy;
		public int buMaxHappy;
		public Info(int lai, int bu) {
			laiMaxHappy = lai;
			buMaxHappy = bu;
		}
	}
	// 得到一个员工,来or不来,分别对应的最大happy!!!!
	public static Info process(Employee x) {
		//1. basecase
		if(x.nexts.isEmpty()) {
			return new Info(x.happy,0);
		}
		int lai = x.happy;
		int bu = 0;
		for(Employee next : x.nexts) {
			Info nextInfo = process(next);// 2. 默得到子树的info信息
			// 3. 整合信息【拆解黑盒,得到info中具体的】
			lai += nextInfo.buMaxHappy;
			bu += Math.max(nextInfo.laiMaxHappy, nextInfo.buMaxHappy);
		}// 4. 返回info
		return new Info(lai,bu);
	}

 

题目三 :最大搜索二叉子树

1. 可能性分析

和x有关,整树是最大搜索二叉树,左边的最大数小于x小于右边的最小数

和x无关

    左树BSTsize大小是最大的

    右树BSTsize大小是最大的

2. 根据可能性分析,列出所有需要的信息

3. 合并第二步的信息,写出需要的信息结构【info构造】

4. 设计递归函数,递归函数是处理以X为头节点的情况下的答案。 包括设计递归的basecase,默认直接得到左树和右树的所有信息【用黑盒】,以及把可能性做整合【拆黑盒】,并且要返回第三步的信息结构这四个小步骤

	public static Node getMaxBST(Node head) {
		return process(head).maxBSTHead;
	}

	public static class ReturnType {
		public Node maxBSTHead;
		public int maxBSTSize;
		public int min;
		public int max;

		public ReturnType(Node maxBSTHead, int maxBSTSize, int min, int max) {
			this.maxBSTHead = maxBSTHead;
			this.maxBSTSize = maxBSTSize;
			this.min = min;
			this.max = max;
		}
	}

	public static ReturnType process(Node X) {
		// base case : 如果子树是空树
		// 最小值为系统最大
		// 最大值为系统最小
		if (X == null) {
			return new ReturnType(null, 0, Integer.MAX_VALUE, Integer.MIN_VALUE);
		}
		// 默认直接得到左树全部信息
		ReturnType lData = process(X.left);
		// 默认直接得到右树全部信息
		ReturnType rData = process(X.right);
		// 以下过程为信息整合
		// 同时以X为头的子树也做同样的要求,也需要返回如ReturnType描述的全部信息
		// 以X为头的子树的最小值是:左树最小、右树最小、X的值,三者中最小的
		int min = Math.min(X.value, Math.min(lData.min, rData.min));
		// 以X为头的子树的最大值是:左树最大、右树最大、X的值,三者中最大的
		int max = Math.max(X.value, Math.max(lData.max, rData.max));
		// 如果只考虑可能性一和可能性二,以X为头的子树的最大搜索二叉树大小
		int maxBSTSize = Math.max(lData.maxBSTSize, rData.maxBSTSize);
		// 如果只考虑可能性一和可能性二,以X为头的子树的最大搜索二叉树头节点
		Node maxBSTHead = lData.maxBSTSize >= rData.maxBSTSize ? lData.maxBSTHead
				: rData.maxBSTHead;
		// 利用收集的信息,可以判断是否存在可能性三
		if (lData.maxBSTHead == X.left && rData.maxBSTHead == X.right
				&& X.value > lData.max && X.value < rData.min) {
			maxBSTSize = lData.maxBSTSize + rData.maxBSTSize + 1;
			maxBSTHead = X;
		}
		// 信息全部搞定,返回
		return new ReturnType(maxBSTHead, maxBSTSize, min, max);
	}

	// for test -- print tree
	public static void printTree(Node head) {
		System.out.println("Binary Tree:");
		printInOrder(head, 0, "H", 17);
		System.out.println();
	}

	public static void printInOrder(Node head, int height, String to, int len) {
		if (head == null) {
			return;
		}
		printInOrder(head.right, height + 1, "v", len);
		String val = to + head.value + to;
		int lenM = val.length();
		int lenL = (len - lenM) / 2;
		int lenR = len - lenM - lenL;
		val = getSpace(lenL) + val + getSpace(lenR);
		System.out.println(getSpace(height * len) + val);
		printInOrder(head.left, height + 1, "^", len);
	}

题目四:是否是平衡二叉树

int TreeDepth(const BinaryTreeNode* pRoot)
{
    if(pRoot == nullptr)
        return 0;

    int nLeft = TreeDepth(pRoot->m_pLeft);
    int nRight = TreeDepth(pRoot->m_pRight);

    return (nLeft > nRight) ? (nLeft + 1) : (nRight + 1);
}

bool IsBalanced_Solution1(const BinaryTreeNode* pRoot)
{
    if(pRoot == nullptr)
        return true;

    int left = TreeDepth(pRoot->m_pLeft);
    int right = TreeDepth(pRoot->m_pRight);
    int diff = left - right;
    if(diff > 1 || diff < -1)
        return false;

    return IsBalanced_Solution1(pRoot->m_pLeft) 
        && IsBalanced_Solution1(pRoot->m_pRight);
}

// ====================方法2====================
bool IsBalanced(const BinaryTreeNode* pRoot, int* pDepth);

bool IsBalanced_Solution2(const BinaryTreeNode* pRoot)
{
    int depth = 0;
    return IsBalanced(pRoot, &depth);
}

bool IsBalanced(const BinaryTreeNode* pRoot, int* pDepth)
{
    if(pRoot == nullptr)
    {
        *pDepth = 0;
        return true;
    }

    int left, right;
    if(IsBalanced(pRoot->m_pLeft, &left) 
        && IsBalanced(pRoot->m_pRight, &right))
    {
        int diff = left - right;
        if(diff <= 1 && diff >= -1)
        {
            *pDepth = 1 + (left > right ? left : right);
            return true;
        }
    }

    return false;
}

题目五:二叉树 最近公共祖先(后序遍历)

 

先用黑盒,返回值是祖先节点。其实就是后序遍历查找p节点或者q节点。

整体思想是:

看看左子树上是否有p或者q,如果没有就返回nullptr,说明到底也没有发现p或者q。那么肯定在右子树上面,所以返回右子树上的查找结果。(第一个找到的就是祖先节点)

右子树上也是同样的道理   ->导致了basecase的催生。

如果左右子树都发现有p或者q,那么直接返回root就好了。

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null||root==p||root==q)//返回叶子结点,或者p节点或者q节点,返回的是第一个遇到的p或者q或者null
            return root;//想看左子树上是否存在p或者q!!!basecase就这么构造
        TreeNode left = lowestCommonAncestor(root.left,p,q);
        TreeNode right = lowestCommonAncestor(root.right,p,q);
        if (left == null) //如果是返回的是叶子结点,说明到了叶子结点也还没有p或者q出现。
            return right;//说明左子树上没有p或者q
        if (right == null) //说明右子树上没有p或者q出现,所以肯定在左子树上
            return left;//返回左子树,这里其实是p节点或者q节点
        return root;//如果都不为空,说明左子树一个,右子树一个,该节点就是祖先节点
    }

题目六: 二叉树的最小深度(后序遍历)

class Solution {
public:
int minDepth(TreeNode* root) {
    // 后续遍历
        if (!root) return 0;
        int L = minDepth(root->left), R = minDepth(root->right);
        // 如果都不为0,返回小的,如果至少有一个为0,选出那个不为0的
        return 1 + (L && R ? min(L, R) : max(L, R));
    }
};
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值