树型DP

简介

在二叉树上做动态规划其实要比其他结构上的动态规划要简单一些,因为他的步骤是相当固定的,从下面的例题中可以看出,例如求整棵树的最大搜索二叉子树,这里有一个非常简单的套路,那就是如果让我们求整棵树的最大搜索二茬子树,那么我们可以去求每一个节点的最大搜索二茬子树,最终答案必在其中。

引子问题

对于一棵二叉树,需要你求出它的最大值和最小值。
解题思路
方案一:
直接遍历二叉树,那样就可以很轻松的获得最大值和最小值了,这里我们不说这种方法
方案二:
我们用递归的方法,整棵树上的最大值和最小值一定在以任意一个节点为头节点的时候的整棵子树上的最大值和最小值之中
解题步骤

  1. 列可能性:作为一个头结点来说,它的最大值可能来自于左子树的最大值,同样可能来自于右树中的最大值,还有可能是自己,共三种可能性
  2. 收集信息:我们的设计在通过递归函数来完成的,因此我们需要将左子树和右子树需要返回给头结点的信息类型返回到头结点,这里我们需要将每棵子树中的最大值和最小值结构返回给头结点
  3. 处理好子树返回的消息,形成自身需要向头结点返回的信息(因为是在递归过程中)
  4. 根据返回值的特点设计base case的返回值

示例代码

#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>
#include <climits>

using namespace std;


class TreeNode 
{
public:
	TreeNode(int x) 
		:val(x)
		,left(nullptr)
		,right(nullptr) 
	{
	}
	int val;
	TreeNode *left;
	TreeNode *right;
	
};
class ReturnData 
{
public:
	ReturnData()
		:m_min(INT32_MAX)
		,m_max(INT32_MIN)
	{

	}
	ReturnData(int max, int min)
		:m_min(min)
		, m_max(max)
	{

	}
public:
	int m_min;
	int m_max;
};

ReturnData process(TreeNode *head)
{
	if (head == nullptr)
	{
		// ReturnData的默认构造函数就代表了我们考虑到上述第四点的base case设计
		return ReturnData(); 
	}
	ReturnData leftData = process(head->left);
	ReturnData rightData = process(head->right);
	// 构造当前头结点代表的子树向上一节点返回的数据
	return ReturnData(
		max(head->val, max(leftData.m_max, rightData.m_max)),
		min(head->val, min(leftData.m_min, rightData.m_min))
		);
}

int main(int argc, char ** argv)
{
	TreeNode * head = new TreeNode(1);
	head->left = new TreeNode(2);
	head->left->right = new TreeNode(3);
	head->left->left = new TreeNode(4);
	head->right = new TreeNode(8);
	head->right->left = new TreeNode(5);
	head->right->right = new TreeNode(6);

	ReturnData rd = process(head);
	cout << rd.m_max << "," << rd.m_min << endl;
	system("pause");
	return EXIT_SUCCESS;
}

时间复杂度:O(N)

求整棵树的最大搜索二叉子树

注意:一个子树就是从某个节点开始,下面所有的东西都要才叫子树。
一、列可能性
1) 最大二茬搜索子树可能来自左子树的某棵子树
2) 最大二茬搜索子树可能来自右子树的某棵子树
3) 左子树整体是搜索二叉树,右树整体也是搜索二叉树,左树的最大值小于当前值,右子树的最小值大于当前值
二、收集信息
为了支持第一种可能性,左树上最大搜索二茬子树的大小、右树最大搜索二茬子树的大小、左树的最大搜索二茬子树的头部、右树的最大搜索二茬子树的头部、左树上的最大值、右树上的最小值。有了以上信息之后足以我们判断是以上三种情况中的哪一种。
三、信息化简
对从第二步汇总的信息进行化简,得到某一棵子树中搜索二叉树的大小、某一棵树中搜索二叉树的头结点、某一棵树中的最大值和最小值。
四、处理消息体

示例代码

#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>
#include <climits>

using namespace std;


class TreeNode 
{
public:
	TreeNode(int x) 
		:val(x)
		,left(nullptr)
		,right(nullptr) 
	{
	}
	int val;
	TreeNode *left;
	TreeNode *right;
	
};
class ReturnData 
{
public:
	ReturnData()
		: m_size(0)
		, m_head(nullptr)
		, m_min(INT32_MAX)
		, m_max(INT32_MIN)
	{

	}
	ReturnData(int size, TreeNode *head, int min, int max)
		: m_size(size)
		, m_head(head)
		, m_min(min)
		, m_max(max)
	{

	}
	int m_size;
	TreeNode *m_head;
	int m_min;
	int m_max;
};

ReturnData process(TreeNode * head)
{
	if (head == nullptr)
	{
		return ReturnData();
	}
	ReturnData leftData = process(head->left);
	ReturnData rightData = process(head->right);

	int includeItSelf = 0;
	if (leftData.m_head == head->left
		&& rightData.m_head == head->right
		&& leftData.m_max < head->val
		&& rightData.m_min > head->val)
	{
		includeItSelf = leftData.m_size + rightData.m_size + 1;
	}
	int lsize = leftData.m_size;
	int rsize = rightData.m_size;
	int maxSize = max(max(lsize, rsize), includeItSelf);
	TreeNode * maxHead = lsize > rsize ? leftData.m_head : rightData.m_head;
	if (maxSize == includeItSelf)
	{
		maxHead = head;
	}
	return  ReturnData(maxSize, maxHead,
		min(min(leftData.m_min, rightData.m_min), head->val),
		max(max(leftData.m_max, rightData.m_max), head->val));
}
int main(int argc, char ** argv)
{
	TreeNode * head = new TreeNode(4);
	head->left = new TreeNode(2);
	head->left->right = new TreeNode(3);
	head->left->left = new TreeNode(1);
	head->right = new TreeNode(6);
	head->right->left = new TreeNode(5);
	head->right->right = new TreeNode(8);

	ReturnData rd = process(head);
	cout << rd.m_head->val << "," << rd.m_size << endl;
	system("pause");
	return EXIT_SUCCESS;
}

求一棵二叉树上的最远距离

二叉树中一个节点可以往上走和往下走,那么从节点A总能走到节点B。节点A到节点B的距离为:A走到B最短路径上节点个数。

解题思路
大的方法论是求以每个节点为头的最长距离是多少

一、可能性进行分析

  1. 最远距离在左子树上,且不经过头结点
  2. 最远距离在右子树上,且不经过头节点
  3. 最远距离为从左子树最大深度到头结点最后到右子树的最大深度

二、 收集信息
子树最大距离和子树最大深度
三、信息简化(无)

示例代码

#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>
#include <climits>

using namespace std;


class TreeNode 
{
public:
	TreeNode(int x) 
		:val(x)
		,left(nullptr)
		,right(nullptr) 
	{
	}
	int val;
	TreeNode *left;
	TreeNode *right;
	
};
class ReturnData 
{
public:
	ReturnData()
		: m_len(0)
		, m_dep(0)
	{

	}
	ReturnData(int len, int dep)
		: m_len(len)
		, m_dep(dep)
	{

	}
	int m_len;
	int m_dep;
};

ReturnData process(TreeNode * head)
{
	if (head == nullptr)
	{
		return ReturnData();
	}
	ReturnData leftData = process(head->left);
	ReturnData rightData = process(head->right);
	int includeHeadDistance = leftData.m_dep + rightData.m_dep + 1;
	int ldep = leftData.m_dep;
	int rdep = rightData.m_dep;
	int resultDistance = max(max(ldep, rdep), includeHeadDistance);
	int resultDep = max(ldep, rdep) + 1;
	return ReturnData(resultDistance, resultDep);

}
int main(int argc, char ** argv)
{
	TreeNode * head = new TreeNode(4);
	head->left = new TreeNode(2);
	head->left->right = new TreeNode(3);
	head->right = new TreeNode(6);
	head->right->left = new TreeNode(5);

	ReturnData rd = process(head);
	cout << rd.m_len << "," << rd.m_dep << endl;
	system("pause");
	return EXIT_SUCCESS;
}

判断一棵树是否为平衡树

一、列可能性

  1. 某一节点的左子树不平衡
  2. 某一节点的右子树不平衡
  3. 某一节点的左子树和右子树都是平衡的

二、收集信息
该子树是否为平衡树、当该子树是平衡的时候其最大高度是多少
三、信息化简(略)

示例代码

#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>
#include <climits>

using namespace std;
class TreeNode
{
public:
	TreeNode(int x)
		:val(x)
		, left(nullptr)
		, right(nullptr)
	{
	}
	int val;
	TreeNode *left;
	TreeNode *right;

};
class ReturnData
{
public:
	ReturnData()
		: m_is_Balance(false)
		, m_dep(0)
	{

	}
	ReturnData(bool is_Balance, int dep)
		: m_is_Balance(is_Balance)
		, m_dep(dep)
	{

	}
	bool m_is_Balance;
	int m_dep;
};
ReturnData process(TreeNode * head)
{
	if (head == nullptr)
	{
		return ReturnData(true, 0);
	}
	ReturnData leftData = process(head->left);
	if (!leftData.m_is_Balance)
	{
		return ReturnData();
	}
	ReturnData rightData = process(head->right);
	if (!rightData.m_is_Balance)
	{
		return ReturnData();
	}
	if (abs(leftData.m_dep - rightData.m_dep) > 1)
	{
		return ReturnData();
	}
	return ReturnData(true, max(leftData.m_dep, rightData.m_dep)+1);
}

bool isBalance(TreeNode *head)
{
	return process(head).m_is_Balance;
}

int main(int argc, char ** argv)
{
	TreeNode * head = new TreeNode(4);
	head->left = new TreeNode(2);
	head->left->right = new TreeNode(3);
	head->left->left = new TreeNode(1);
	head->right = new TreeNode(6);
	head->right->left = new TreeNode(6);
	head->right->right = new TreeNode(6);
	cout << isBalance(head) << endl;
	system("pause");
	return EXIT_SUCCESS;
}

最大活跃度问题

一个公司中的上下级关系是一棵多叉树,这个公司要举办晚会,你作为组织者已经摸清了大家的心理:一个员工的直接上级如果到场,这个员工肯定不会来,每个员工都有一个活跃度的值,你可以决定某一个人来或者不来,怎样使得舞会的气氛最活跃,返回最大的活跃值。

示例代码

#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>
#include <climits>

using namespace std;

int maxHappy(vector<vector<int>> &matrix) {
	vector<vector<int>> dp;
	dp.resize(matrix.size());
	vector<bool> visited;
	visited.resize(matrix.size());
	int root = 0;
	// 找boss
	for (int i = 0; i < matrix.size(); i++) {
		if (i == matrix[i][0]) 
		{
			root = i;
		}
	}

	process(matrix, dp, visited, root);
	return max(dp[root][0], dp[root][1]);
}

void process(vector<vector<int>> &matrix, vector<vector<int>> &dp, vector<bool> &visited, int root) 
{
	visited[root] = true;
	dp[root][1] = matrix[root][1];
	for (int i = 0; i < matrix.size(); i++) {
		if (matrix[i][0] == root && !visited[i]) 
		{ // 是该领导的员工,并且不是领导自己
			process(matrix, dp, visited, i);
			dp[root][1] += dp[i][0];
			dp[root][0] += max(dp[i][1], dp[i][0]);
		}
	}
}


int main(int argc, char ** argv)
{
	//第一个值表示角标为i的职工的直接上级,第二个值表示其活跃度
	vector<vector<int>>matrix{ { 1, 8 },{ 1, 9 },{ 1, 10 } };
	system("pause");
	return EXIT_SUCCESS;
}

判断一棵树是否为完全二叉树

方法按层遍历,遍历到任何一个节点,如果该节点有右孩子没有左孩子,直接返回false,不违反1的情况下,如果当前左右两个节点不全,那么,接下来遇到的所有节点都必须是叶节点。

bool isCBT(Node *head)
{
	if (head == nullptr)
	{
		return true;
	}
	queue<Node *> Myqueue;
	bool leaf = false;
	Node *left = nullptr;
	Node *right = nullptr;
	Myqueue.push(head);
	while (!Myqueue.empty())
	{
		head = Myqueue.front();
		Myqueue.pop();
		left = head->left;
		right = head->right;
		// 这里就是情况二和情况一。
		if ((leaf && (left != nullptr || right != nullptr))
			|| (left == nullptr && right != nullptr))
		{
			return false;
		}

		if (left != nullptr)
		{
			Myqueue.push(left);
		}
		// 后面的判断leaf = true之所以可以这样写是因为我们在第一个if中已经排除来左孩子为空,
		// 右孩子不为空的情况。
		if (right != nullptr)
		{
			Myqueue.push(right);
		}
		else
		{
			leaf = true;
		}
	}
	return true;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值