这题依旧利用二叉搜索树中序遍历是单调递增的性质,只要以中序遍历,对比其相邻节点差值即可。
利用到了双指针技巧,使用一个pre指针记录上一个节点,cur代表当前节点。
TreeNode* pre = nullptr;
int minDiff = 100001;
void traversal(TreeNode* cur) {
if (!cur)
return;
traversal(cur->left);
if (pre) {
int diff = abs(cur->val - pre->val);
minDiff = std::min(diff, minDiff);
}
pre = cur;
traversal(cur->right);
return;
}
int getMinimumDifference(TreeNode* root) {
traversal(root);
return minDiff;
}
与上题思路差不多,都是利用到了二叉搜索树中序遍历单调递增的性质,以及双指针。
实现上多了点小技巧:使用全局变量分别记录目前出现的最大频率与当前数出现的频率,在当前数频率大于等于最大频率时对结果数组进行更新。
int count = 1;
int maxCount = 1;
TreeNode* pre = nullptr;
vector ans;
// 依然利用二叉搜索树中序遍历后序列递增的特性
void traversal(TreeNode* cur) {
if (!cur)
return;
// 左
traversal(cur->left);
// 中
if (pre) {
// 与中序上一个节点相同时进行更新
if (cur->val == pre->val) {
++count;
// 当前频率等于最高频率时加入数组
if (count == maxCount)
ans.push_back(cur->val);
// 当前频率大于最高频率时更新最高频率和结果数组
else if (count > maxCount) {
maxCount = count;
ans = { cur->val };
}
}
// 不同时重置当前频率
else {
count = 1;
// 应对最高频率为1的情况
if (count == maxCount)
ans.push_back(cur->val);
}
}
else {
ans.push_back(cur->val);
}
pre = cur;
// 右
traversal(cur->right);
}
vector findMode(TreeNode* root) {
traversal(root);
return ans;
}
这题的重点在于做好情况分类:
情况1:两节点分别位于公共祖先的两子树上
情况2:节点本身就是目标节点,另一目标节点分布在其子树之一上。
递归写法1:后序遍历,使用两个返回值分布记录左右子树释放出现过目标节点
TreeNode* ans = nullptr;
TreeNode* target1;
TreeNode* target2;
// 递归写法1
std::pair traversal(TreeNode* cur) {
if (!cur)
return { false, false };
if (ans)
return { true, true };
// 左
auto left = traversal(cur->left);
// 右
auto right = traversal(cur->right);
// 中
std::pair res;
// 子树之一出现过目标节点 或 节点自身就是目标节点
res.first = left.first || right.first || cur == target1;
res.second = left.second || right.second || cur == target2;
// 只有第一个出现的公共祖先用于更新ans
if (!ans && res.first && res.second)
ans = cur;
return res;
}
// 后序遍历,每个节点都判断其子树是否出现过p和q
TreeNode* lowestCommonAncestor(TreeNode* root) {
target1 = p;
target2 = q;
traversal(root);
return ans;
}
递归写法2:只需要一个返回值,因为同一个目标值不可能在左右子树同时出现(即当左子树找到p(写法1中第一个bool判断为true)时,右子树必定不会有p(第一个bool只能是false))
· 返回值类型:bool,返回当前子树是否包含任一目标节点
· 传入参数:
TreeNode* cur:传入当前节点指针
· 终止条件:
1、当前节点为空节点时返回false
2、找到结果(ans不为空指针)时返回true(目的是剪枝,返回啥无关紧要)
· 单层递归逻辑——后序遍历:根节点不断收集左右子树的信息
左:递归判断左子树,接受左子树中目标节点的信息。如果左子树中找到了目标节点,结果为true。
右:递归判断右子树,接受右子树中目标节点的信息。如果右子树中找到了目标节点,结果为true。
中:
对应情况1:左右结果同时为true,两个节点都在其子树中,更新ans。
对应情况2:节点自身就是目标节点,左右结果任一为true,更新ans。
TreeNode* ans = nullptr;
TreeNode* target1;
TreeNode* target2;
bool traversal(TreeNode* cur) {
if (!cur)
return false;
if (ans)
return true;
bool left = traversal(cur->left);
bool right = traversal(cur->right);
bool middle = cur == target1 || cur == target2;
// 只有第一个出现的公共祖先用于更新ans
if (!ans) {
// 情况1:两个目标节点都在子树中
if (left && right)
ans = cur;
// 情况2:节点自身就是目标节点,子树之一中有另一目标节点
else if (middle && (left || right))
ans = cur;
}
return middle || left || right;
}
// 后序遍历,每个节点都判断其子树是否出现过p和q
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
target1 = p;
target2 = q;
traversal(root);
return ans;
}
递归写法3(比较巧妙,自己估计想不出来):类似冒泡排序,把最近公共祖先不断冒泡传上去
· 返回值类型:TreeNode*,返回最近公共祖先的节点指针
· 传入参数:
TreeNode* cur:当前节点指针
· 终止条件:
1、当前节点为空节点时返回空指针
2、当前节点为任一目标节点时返回自身(开始向上冒泡)
· 单层递归逻辑——后序遍历:根节点不断收集左右子树的信息
左:递归判断左子树,接受左子树中目标节点的信息。如果左子树中找到了目标节点,那么该节点会被冒泡传上来,没找到时传上来的是nullptr。
右:递归判断右子树,接受右子树中目标节点的信息。如果右子树中找到了目标节点,那么该节点会被冒泡传上来,没找到时传上来的是nullptr。
中:
1:左右子树结果都非空,说明两个目标节点分布在两侧子树,符合情况1,该节点是最近公共祖先,返回该节点(开始向上冒泡)
2、左右子树结果任一为空,说明一边子树找到了目标节点,返回非空的那一侧(找到的那个目标节点继续向上冒泡)
3、(那么情况2呢?)情况2实际被终止条件2包含了,情况2下,终止条件2传上去的那个节点即为最近公共祖先,它在向上冒泡的过程不会被更新(因为除它自身外的另一个目标节点在它的下面)。
TreeNode* target1;
TreeNode* target2;
TreeNode* traversal(TreeNode* cur) {
// 终止条件1:空节点时返回空指针
if (!cur)
return nullptr;
// 终止条件2:该节点等于任一目标节点则返回该节点
if (cur == target1 || cur == target2)
return cur;
// 左
TreeNode* left = traversal(cur->left);
// 右
TreeNode* right = traversal(cur->right);
// 中(以下三种分支配合终止条件2可以所有情况)
if (left && right)
return cur;
else if (left && !right)
return left;
else
return right;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
target1 = p;
target2 = q;
return traversal(root);
}