669. 修剪二叉搜索树
给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。
思路:根据题意可以有如下判断:
当我root的值比左边界小的时候,利用二叉搜索树的性质,可以向右继续遍历,继续做判断
当root的值比右边界大的时候,那么就向左遍历。
这两点不容易想,很容易忘。所以需要加强练习。
递归逻辑在注释里面:
class Solution {
public:
// 1、确定递归函数的参数和返回值 因为是要遍历整棵树,做修改,
// 其实不需要返回值也可以,我们也可以完成修剪(其实就是从二叉树中移除节点)的操作。
// 但是有返回值,更方便,可以通过递归函数的返回值来移除节点。
TreeNode* trimBST(TreeNode* root, int low, int high) {
// 2、确定函数的终止条件
// 修剪的操作并不是在终止条件上进行的,所以就是遇到空节点返回就可以了。
if (root == nullptr) return nullptr;
// 3、确定单层递归逻辑
// 3.1 如果root(当前节点)的元素小于low的数值,那么应该递归右子树,并返回右子树符合条件的头结点。
if (root->val < low) {
TreeNode* right = trimBST(root->right, low, high);
return right;
}
// 3.2 如果root(当前节点)的元素大于high的,那么应该递归左子树,并返回左子树符合条件的头结点。
if (root->val > high) {
TreeNode* left = trimBST(root->left, low, high);
return left;
}
// 3.3 遍历整个树 如果root(当前节点)的元素大于high的,那么应该递归左子树,并返回左子树符合条件的头结点。
root->left = trimBST(root->left, low, high);
root->right = trimBST(root->right, low, high);
return root;
}
};
总结: 本题的主要难点在于可以会判断左或者右满足条件,然后直接返回nullptr了,这样会导致删除的节点右子树或者左子树满足条件的情况被删除,缺少结果,所以要注意
本题还联动了前一天的删除二叉搜索树的节点那一题,那一题是删除单个节点,而这一题是删除一连串不满足条件的结果,但是思路很很像,需要再去理解。
108.将有序数组转换为二叉搜索树
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
在 654.最大二叉树里面涉及到了给定一个数组构建一个二叉树,本题的思路和654这题一样,只需要保证每次的中间节点是数组中间的节点即可。
然后就是一个循环不变量的规则,左闭右开和左闭右闭一定要全程统一,否则在递归的过程会导致出现错误。
左闭右闭:
class Solution {
public:
TreeNode* traversal(vector<int>& nums, int left, int right) {
if (left > right) return nullptr;
int mid = (right + left) / 2;
TreeNode* node = new TreeNode(nums[mid]);
node->left = traversal(nums, left, mid - 1);
node->right = traversal(nums, mid + 1, right);
return node;
}
TreeNode* sortedArrayToBST(vector<int>& nums) {
return traversal(nums, 0, nums.size()-1);
}
};
总结:本题在开始自己做的时候,在取中间节点的时候使用了 nums.size() / 2
这样会导致错误。应该用 (right + left) / 2; 在二分法中还要考虑左右加导致溢出,但是在数组中,这个一般不会导致溢出。
538.把二叉搜索树转换为累加树
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
节点的左子树仅包含键 小于 节点键的节点。
节点的右子树仅包含键 大于 节点键的节点。
左右子树也必须是二叉搜索树。
思路:本题的题目比较难读懂,每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
其实这就是一棵树,大家可能看起来有点别扭,换一个角度来看,这就是一个有序数组[2, 5, 13],求从后到前的累加数组,也就是[20, 18, 13],是不是感觉这就简单了。
转变了思路以后,就是一个右中左的遍历顺序:
class Solution {
public:
int pre = 0;
void traverasl(TreeNode* node) {
if (node == nullptr) return;
traverasl(node->right);
node->val += pre;
pre = node->val;
traverasl(node->left);
}
TreeNode* convertBST(TreeNode* root) {
pre = 0;
traverasl(root);
return root;
}
};
class Solution {
public:
void traversal(TreeNode* root) {
stack<TreeNode*> st;
TreeNode* cur = root;
int pre = 0;
while (cur != nullptr || !st.empty()) {
if (cur != nullptr) {
st.push(cur);
cur = cur->right; // 右
}
else {
cur = st.top(); // 中
st.pop();
cur->val += pre;
pre = cur->val;
cur = cur->left; // 左
}
}
}
TreeNode* convertBST(TreeNode* root) {
int pre = 0;
traversal(root);
return root;
}
};
总结:本题很简单,但是题目比较难读懂,但是如果用数组做了类比以后,就变得容易理解了,然后还是要多练才行。
二叉树总结篇
在每一道二叉树的题目中,我都使用了递归三部曲来分析题目,相信大家以后看到二叉树,看到递归,都会想:返回值、参数是什么?终止条件是什么?单层逻辑是什么?
按照代码随想录网站上的总结顺序进行复盘
二叉树收获比较多,因为之前没有学完全学过二叉树,跟着卡哥刷题,慢慢的就学会了很多,收获真的很大。