235. 二叉搜索树的最近公共祖先
与236类似但可以利用二叉搜索树的性质来做
二叉搜索树:左子 小于头 小于右子
(怪怪的 感觉是不是先记住比较好)(而且也没太理解二叉搜索树的规律)
大体思路:从上到下遍历(从头节点开始遍历,那应该是前序遍历)
公共祖先的节点的值应该在p和q的闭区间内,最近公共祖先是从上到下遍历中遇到的第一个符合条件的节点(至于为什么是第一个我也没捋明白)
写了一下没写出来发现我没搞清楚“沿一条边递归”和“沿整棵树递归”的区别:
在二叉树:公共祖先问题 (opens new window)中,如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树。
在递归函数有返回值的情况下:如果要搜索一条边,递归函数返回值不为空的时候,立刻返回,如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)。
很明显这里的情况是“返回值不为空的时候立即返回”,所以是搜索一条边。
701.二叉搜索树中的插入操作
遍历二叉树,其实可以不考虑题目中提示所说的改变树的结构的插入方式,遇到空节点就插入就行。
递归插入,单层递归逻辑:
入参:
root,val
返回值可以为空(?)其实在这里感觉有没有返回值都行,但是没有的话可能无法知道节点到底有没有插入从而终止递归,有返回值的话返回的节点赋值给?
可以有,也可以没有,但递归函数如果没有返回值的话,实现是比较麻烦的,下面也会给出其具体实现代码。
有返回值的话,可以利用返回值完成新加入的节点与其父节点的赋值操作。
与当前节点比较,大于就往节点右侧插,小于就往节点左侧插
确定终止条件:
当前节点为null说明可以插入
因此可以根据终止条件得知,返回的当前节点由上一次接住:
用java写了一版:
class Solution {public TreeNode insertIntoBST(TreeNode root, int val) {if (root == null) {return new TreeNode(val);}if (root.val > val) {root.left = insertIntoBST(root.left, val);} else {root.right = insertIntoBST(root.right, val);}return root;}
}
450.删除二叉搜索树中的节点
也可以沿着边搜索,遇到删除的节点分三种情况判断:
1.叶子节点,直接返回null
2.左子右子只有一个存在:返回存在的子节点
3.左右子都存在:右子接到该节点的位置,左子接到右子树上最左的叶子节点下,返回右节点
既然有返回值,那么递归的过程中就需要接住这个返回值。
接住的逻辑:
这里相当于把新的节点返回给上一层,上一层就要用 root->left 或者 root->right接住,代码如下:
if (root->val > key) root->left = deleteNode(root->left, key);
if (root->val < key) root->right = deleteNode(root->right, key);
return root;
开写:
class Solution {
public:TreeNode* deleteNode(TreeNode* root, int key) {if (root == nullptr)return root;if (root->val == key) {if (root->left == nullptr && root->right == nullptr) {return nullptr;} else if (root->left == nullptr || root->right == nullptr) {return root->left == nullptr ? root->right : root->left;} else {findLeaf(root->right)->left = root->left;return root->right;}}if (root->left)root->left = deleteNode(root->left, key);if (root->right)root->right = deleteNode(root->right, key);return root;}TreeNode* findLeaf(TreeNode* root) {while (root->left != nullptr) {root = root->left;}return root;}
};
AC了 上面黑体的部分算是解本题的关键点,可以记住。
669. 修剪二叉搜索树
如果找到不在范围内的节点,
小于最小值,说明该节点及其左子树都不要了
大于最大值,说明该节点及其右子树都不要了
递归逻辑:
记住递归是需要找到本层逻辑,不需要考虑整个递归的套路
本层逻辑:
先判断当前节点,
小于范围:对该节点进行一次删除操作,把节点当作没有左子树的情况返回右节点
大于范围:当作没有右子树的情况返回左节点
再执行删除节点的接住逻辑
开写的时候发现了一个忽略掉的逻辑:
就是把左或右子树接到当前节点上的时候,会忽略对新接入节点的判断:
比如
比如这种情况,如果low=3, 删除了0后直接接上的2其实也不在范围内
又绕进去了。。用这个逻辑好像不太对
class Solution {
public:TreeNode* trimBST(TreeNode* root, int low, int high) {if (root == nullptr)return root;if (root->val < low) {return root->right;} else if (root->val > high) {return root->left;}if (root->left)root->left = trimBST(root->left, low, high);if (root->right)root->right = trimBST(root->right, low, high);return root;}
};
写了一下果然不对,卡在这个用例了:
看了一下题解,其实单层递归的逻辑跟这个很类似:
确定单层递归的逻辑
如果root(当前节点)的元素小于low的数值,那么应该递归右子树,并返回右子树符合条件的头结点。
也就是说其实不能直接返回整个右子树,需要返回右子树中符合条件的头节点
那么如何找到右子树中符合条件的头节点?使用本递归函数进行修剪!
在单层递归中调用递归函数的时候,其实不用考虑递归的整个过程,直接把这个递归函数当作一个实现功能的工具即可!
就是说:
if (root->val < low) {
TreeNode* right = trimBST(root->right, low, high); // 寻找符合区间[low, high]的节点
return right;
}
开写!
class Solution {
public:TreeNode* trimBST(TreeNode* root, int low, int high) {if (root == nullptr)return root;if (root->val < low) {return trimBST(root->right, low, high);} else if (root->val > high) {return trimBST(root->left, low, high);}if (root->left) {root->left = trimBST(root->left, low, high);}if (root->right) {root->right = trimBST(root->right, low, high);}return root;}
};
其实只需要在第一版代码的基础上稍微修改“返回左右子树中符合条件的 节点” 即可。
108.将有序数组转换为二叉搜索树
做这道题目之前大家可以了解一下这几道:
106.从中序与后序遍历序列构造二叉树 (opens new window)
654.最大二叉树 (opens new window)中其实已经讲过了,如果根据数组构造一棵二叉树。
701.二叉搜索树中的插入操作 (opens new window)
450.删除二叉搜索树中的节点
一开始被卡住了,卡住的点在于看到题干给出的两个例子:
一直在想为什么不能说是0左右直接长出长长的两撇(这不算平衡二叉树吗,为什么非要调换-10和-3的位置把-3放在-10的右子)
(查了一下好像本来就可以产生很多答案,姑且把我的构建结果当作正确的一种)
单层递归思路:
入参感觉可以有数组,当前头节点,int left, int right
这里注意:
再来看参数,首先是传入数组,然后就是左下标left和右下标right,我们在二叉树:构造二叉树登场! (opens new window)中提过,在构造二叉树的时候尽量不要重新定义左右区间数组,而是用下标来操作原数组。
首先找到本层传入数组的中点
赋给一个构造出的节点,
该节点的左右节点,根据traversal函数获得
返回该节点
递归终止条件
这里定义的是左闭右闭的区间,所以当区间 left > right的时候,就是空节点了。
开写
class Solution {
public:TreeNode* sortedArrayToBST(vector<int>& nums) {return traversal(nums, 0, nums.size()-1);}TreeNode* traversal(vector<int>& nums, int left, int right){if(left > right) return nullptr;int mid = left + ((right - left)>>1);TreeNode* root = new TreeNode(nums[mid]);root->left = traversal(nums, left,mid-1);root->right = traversal(nums, mid+1, right);return root;}
};
538.把二叉搜索树转换为累加树
本题与1038题相同
需要写出一个求和的封装,然后遍历该二叉树根据求和替换每个节点的值。
求和就是求出右子树的和 再加 当前节点值
求和过程的递归就是正常遍历就行,给出当前节点的右子树的头节点,遍历一圈获取和(理论上是怎么遍历都可以)
写了一下发现不太对。。没法对应上这个图
圈起来的部分全没法使用上述方法替换val(节点5甚至没有右子树)
感觉也可以将二叉树转换成数组,不过这样可以获取但替换不太方便
还是考虑在遍历过程中按顺序求值并替换,可以按右-〉头-〉左的顺序遍历并替换
每遍历一个节点就把当前值+=sum
class Solution {
public:int sum = 0;TreeNode* convertBST(TreeNode* root) {sum=0;traversal(root);return root;}void traversal(TreeNode* root) {if (root == nullptr)return;if (root->right != nullptr){traversal(root->right);}root->val += sum;sum = root->val;if (root->left != nullptr)traversal(root->left);return;}};
改用java写一写。。:
class Solution {private int sum = 0;public TreeNode convertBST(TreeNode root) {getSum(root);return root;}public void getSum(TreeNode root){if(root==null) return;getSum(root.right);root.val += sum;sum=root.val;getSum(root.left);}
}
最后部分是对二叉树模块总结:
二叉树模块挺全面的,卡哥每题都给出了递归和迭代的解法,但我这次一刷基本上只选择了递归的方法(除了有些层序遍历会用迭代)
而且二叉树这个模块我中间间隔了很久才重新开始刷。。中间经历了找实习+写毕设+回国实习。。实习从C++转到Java。。真的隔了很久。。前面一些二叉树的题(二叉树属性,修改与改造部分)急需二刷
就先这样,一刷二叉树over!!