2. 二叉树染色
目录
题目
小扣有一个根结点为 root 的二叉树模型,初始所有结点均为白色,可以用蓝色染料给模型结点染色,模型的每个结点有一个 val 价值。小扣出于美观考虑,希望最后二叉树上每个蓝色相连部分的结点个数不能超过 k 个,求所有染成蓝色的结点价值总和最大是多少?
示例1:
输入:
root = [5,2,3,4], k = 2
输出:
12
解释:
结点 5、3、4 染成蓝色,获得最大的价值 5+3+4=12
示例2:
输入:
root = [4,1,3,9,null,null,2], k = 2
输出:
16
解释:结点 4、3、9 染成蓝色,获得最大的价值 4+3+9=16
提示:
1 <= k <= 10
1 <= val <= 10000
1 <= 结点数量 <= 10000
思路
首先先用暴力破解的方式去尝试:
每次遇到一个节点的时候,对该节点的操作有两种选择。第一种选择选择当前节点,即root节点,还有一个选择就是不选择root节点。那么当前节点的值就有两种:
第一种是选择root节点,然后左右子节点与root相连不超过k的最大值是什么。但是就要讨论,root节点分配之后,k值少1,即左节点与root节点相连的个数与右节点与root相连的个数之和不超过k - 1。然后在对左右节点调用dfs的值的时候,将k值设置成限制的值。
第二种就是不选择root节点,左右两个节点都可以单独看作成一个树来讨论问题,因为root节点不选择,所以左右两个子节点之间没有任何关系,而当前的最大值则变成了左节点的最大值加上右节点的最大值之和。递归调用的时候k值直接设置成题目要求。
然后比较这两种选择之后返回最大值。其实就是一个递归问题。
暴力破解算法如下:
int dfs(TreeNode* root, int k){
if(!root) return 0;
int sum = 0;
// 选择root节点的循环
if(k > 0){
for(int i = 0; i < k; i++){
int left = dfs(root -> left, i);
int right = dfs(root -> right, k - 1- i);
sum = max(sum, left + right + root -> val); // 储存最大值
}
}
// 不选择root节点的情况
int left = dfs(root -> left, k);
int right = dfs(root -> right, k);
sum = max(sum, left + right); // 比较选择与不选择之间谁大
return sum;
}
问题
但是很明显,就是递归的时候,会反复计算重复的值,所以,即是递归,又有重复子问题,那么想到什么??
动态规划!!
那么我们只要保存好当前节点在当前k情况下的值即可。然后在访问的时候就直接查备忘录。
class Solution {
public:
int _k;
unordered_map<TreeNode *, int> map;
int count = 0;
int dp[10002][11] = {{-1}}; // 第一个是节点索引的映射,第二个是相对应的K值
int maxValue(TreeNode* root, int k) {
_k = k;
memset(dp, -1, sizeof(dp)); // 开辟备忘录
return dfs(root, k);
}
int dfs(TreeNode* root, int k){
if(!root) return 0;
// 若不在备忘录内,则写入备忘录
if(map.find(root) == map.end()){
map.insert(make_pair(root, count));
count++;
}
int idx = map[root];
int sum = 0;
if(dp[idx][k] != -1) return dp[idx][k]; // 如果备忘录有,则获取
// 选择root节点的情况
for(int i = 0 ; i < k; i++){
int left = dfs(root -> left, i);
int right = dfs(root -> right, k - 1 - i);
sum = max(sum, left + right + root -> val);
}
// 不选择root节点的情况
int nosum = dfs(root -> left, _k) + dfs(root -> right, _k);
sum = max(sum, nosum);
dp[idx][k] = sum; // 将最大值储存在备忘录内,该节点在K值情况的最大值
return sum;
}
};
大佬代码:
比赛结束之后看了高手的题,简介明了,我可以相对的解释一下。
代码如下
class Solution {
public:
vector<int> dfs(TreeNode* root, int k){
if (root==NULL){
vector<int> res;
res.push_back(0); // 返回数组,代表为空的时候,即有一个0
return res;
}
vector<int> vl=dfs(root->left ,k);
vector<int> vr=dfs(root->right ,k);
vector<int> res;
res.resize(k+1); // 值最大为k但是数组从0开始算
for (int i=0;i<vl.size();i++)
for (int j=0;j<vr.size();j++){
res[0]=max(res[0],vl[i]+vr[j]); // 不选择当前节点的最大值
if (i+j+1<=k)
res[i+j+1]=max(res[i+j+1],vl[i]+vr[j]+root->val); // 选择当前节点的最大值
}
return res;
}
int maxValue(TreeNode* root, int k) {
vector<int> result=dfs(root,k);
int ans=0;
for (int i=0;i<=result.size();i++)
if (i<=k) ans=max(ans,result[i]); // 比较所有可能性
return ans;
}
};
dfs每次都会返回一个数组,数组代表的是{0, 1, 2 ... k}。
数组的索引代表的是与该节点相连的点的个数。比如返回一个 vl 为{ 4 ,2, 6 }。代表与左节点相连为0个的最大值为4(即不选择左节点时,左节点的最大值)。第二个为与左节点相连的点的个数为1的最大值(其实就是只选了左节点,然后左节点的子节点与左节点断开连接)。以此类推。
if (i+j+1<=k)
res[i+j+1]=max(res[i+j+1],vl[i]+vr[j]+root->val);
而这一个代表的是,选择root节点和i个左节点和j个右节点,总数不超过k的最大值。
总结:
大佬的代码太简洁了,我还得多加学习。
第一次写这个,如果题目侵权的话就删删。