[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));
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章