樹形dp套路 樹形dp套路使用前提: 如果題目求解目標是S規則,則求解流程可以定成以每一個節點爲頭節點的子樹在S規則下的每一個答案,並且最終答案一定在其中
https://www.cnblogs.com/mhpp/p/6628548.html 這其中是一些其他的例子,抽空可以看看。
目錄
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));
}
};