首先我们看Leetcode543题二叉树的直径问题
题目描述:
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
示例 :
给定二叉树1
/ \
2 3
/ \
4 5
返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。注意:两结点之间的路径长度是以它们之间边的数目表示。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/diameter-of-binary-tree
下面给出解法:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int ans = 0;
int diameterOfBinaryTree(TreeNode* root)
{
TreeDP(root);
return ans;
}
int TreeDP(TreeNode* root)
{
if (root == nullptr)
return -1;
int left = TreeDP(root->left) + 1;
int right = TreeDP(root->right) + 1;
ans = max(ans,left + right);
return max(left,right);
}
};
算法介绍:
首先我们先分析题目什么是二叉树的直径,如下图所示,红色部分即是二叉树的直径,可以经过根节点也可以不经过根节点。
那么我们该如何求二叉树的直径呢,学过数据结构的同学可能知道,二叉树具有着天然的和原问题相似的子问题结构,在求树的深度的时候我们就可将树分为左子树,右子树和根,分别对左右子树递归得到树的深度。
二叉树的深度 = max(左子树的深度,右子树的深度) + 1
同理,在求二叉树的直径上面我们也可以对直径进行划分,一个树的直径可以由左子树的最大深度 + 右子树的最大深度 + 2(根节点分别到左子树和右子树的边)决定。而要求左右子树的最大深度,我们要分别对左右子树进行递归,递归计算树的深度的同时,我们也可以计算以该子树的直径,并用一个值ans来维护,找到ans的最大值即为我们要求的二叉树直径的最大值。
我们来解析代码递归的核心部分:
int TreeDP(TreeNode* root)
{
if (root == nullptr) //当节点不存在时无节点返回-1
return -1;
int left = TreeDP(root->left) + 1;//计算左子树的深度 + 根节点到左子树的一条边
int right = TreeDP(root->right) + 1;//计算右子树的深度 + 根节点到右子树的一条边
ans = max(ans,left + right);//用ans值维护,现在的直径和计算过的直径对比,寻找最大值
return max(left,right);//找到左右子树深度的最大值,用于判断当前节点的最大深度
}
接下来我们来看一个进阶的题目,Leetcode2246相邻字符不同的最长路径问题
题目描述:
给你一棵 树(即一个连通、无向、无环图),根节点是节点 0 ,这棵树由编号从 0 到 n - 1 的 n 个节点组成。用下标从 0 开始、长度为 n 的数组 parent 来表示这棵树,其中 parent[i] 是节点 i 的父节点,由于节点 0 是根节点,所以 parent[0] == -1 。
另给你一个字符串 s ,长度也是 n ,其中 s[i] 表示分配给节点 i 的字符。
请你找出路径上任意一对相邻节点都没有分配到相同字符的 最长路径 ,并返回该路径的长度。
示例 1:
输入:parent = [-1,0,0,1,1,2], s = "abacbe"
输出:3
解释:任意一对相邻节点字符都不同的最长路径是:0 -> 1 -> 3 。该路径的长度是 3 ,所以返回 3 。
可以证明不存在满足上述条件且比 3 更长的路径。
示例 2:输入:parent = [-1,0,0,0], s = "aabc"
输出:3
解释:任意一对相邻节点字符都不同的最长路径是:2 -> 0 -> 3 。该路径的长度为 3 ,所以返回 3 。来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/longest-path-with-different-adjacent-characters
下面给出解法:
class Solution {
public:
int ans = 0;
vector<vector<int>> lists;
string sl;
int longestPath(vector<int>& parent, string s)
{
int n = parent.size();
for (int i = 0;i < n;i++)
{
vector<int> list;
lists.push_back(list);
}
for (int i = 1;i < n;i++)
lists[parent[i]].push_back(i);
sl = s;
TreeDP(0);
return ans + 1;
}
int TreeDP(int node)
{
int maxlenth = 0;
for (int i : lists[node])
{
int currentlenth = TreeDP(i) + 1;
if (sl[i] != sl[node])
{
ans = max(ans,maxlenth + currentlenth);
maxlenth = max(maxlenth,currentlenth);
}
}
return maxlenth;
}
};
题目解析:
本题在求二叉树的直径的基础上改变而来,增加了相邻两个点值不相同的情况。但二叉树只有左子树和右子树,本题树的子树个数是不确定的,所以我们要遍历所有子树,于是我们用一个集合来记录所有节点的子树情况,遍历集合即可。
本题中我们需要找到直径的最大值,则我们需要找到子树的最大值和次最大值相加即可得到直径最大。于是我们用maxlength来维护当前节点的子树的最大值,我们遍历所有的子树的同时更新最大值,并且与当前正在遍历的子树相加,用ans值来维护相加的最大值,即可得到最大直径。
由于本题要求相邻两个节点不相同,所以我们加入sl[i] != sl[node],保证相邻节点不同时在更新ans即可
核心代码解析:
int TreeDP(int node)
{
int maxlenth = 0;
for (int i : lists[node])
{
int currentlenth = TreeDP(i) + 1;//递归当前树的深度
if (sl[i] != sl[node])
{
ans = max(ans,maxlenth + currentlenth);//维护答案最大值
maxlenth = max(maxlenth,currentlenth);//更新当前节点树深度的最大值
}
}
return maxlenth;
}