二叉搜索树的最近公共祖先
上一天刚写过二叉树的最近公共祖先
对于上题的解法肯定也适用于二叉搜索树的最近公共祖先。但是我们要利用二叉搜索树的性质进一步优化代码。
因为是有序树,所有 如果 中间节点是 q 和 p 的公共祖先,那么 中节点的数组 一定是在 [p, q]区间的。即 中节点 > p && 中节点 < q 或者 中节点 > q && 中节点 < p。
那么只要从上到下去遍历,遇到 cur节点是数值在[p, q]区间中则一定可以说明该节点cur就是p 和 q的公共祖先。 那问题来了,一定是最近公共祖先吗?
如图,我们从根节点搜索,第一次遇到 cur节点是数值在[q, p]区间中,即 节点5,此时可以说明 q 和 p 一定分别存在于 节点 5的左子树,和右子树中。
此时节点5是不是最近公共祖先? 如果 从节点5继续向左遍历,那么将错过成为p的祖先, 如果从节点5继续向右遍历则错过成为q的祖先。
所以当我们从上向下去递归遍历,第一次遇到 cur节点是数值在[q, p]区间中,那么cur就是 q和p的最近公共祖先。
理解这一点,本题就很好解了。
代码:
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null) return null;
if(root.val>p.val &&root.val>q.val){
TreeNode left = lowestCommonAncestor(root.left, p,q);
if(left !=null) return left;
}
if(root.val<p.val && root.val<q.val){
TreeNode right = lowestCommonAncestor(root.right,p,q);
if(right !=null) return right;
}
return root;
}
}
如果 cur->val 大于 p->val,同时 cur->val 大于q->val,那么就应该向左遍历(说明目标区间在左子树上)。
如果 cur->val 小于 p->val,同时 cur->val 小于 q->val,那么就应该向右遍历(目标区间在右子树)。
剩下的情况,就是cur节点在区间(p->val <= cur->val && cur->val <= q->val)或者 (q->val <= cur->val && cur->val <= p->val)中,那么cur就是最近公共祖先了,直接返回cur。
根据这个思路写迭代法也很简单:
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while(root !=null){
if(root.val>p.val && root.val>q.val) root = root.left;
else if(root.val<p.val && root.val < q.val) root = root.right;
else return root;
}
return root;
}
}
二叉搜索树的迭代方法,要先思考直接利用二叉搜索树性质循环,很多时候不需要栈或者队列来辅助就能实现迭代版本。
二叉搜索树中的插入操作
自己思路:根据二叉搜索树的性质,利用迭代的方法找到节点的插入位置
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if(root == null) {
return new TreeNode(val);
}
TreeNode cur = root;
TreeNode pre = null ;
while(cur !=null){
pre = cur;
if(cur.val > val){
cur = cur.left;
}else if(cur.val< val){
cur = cur.right;
}
}
if(pre.val > val) pre.left = new TreeNode(val);
if(pre.val < val) pre.right = new TreeNode(val);
return root;
}
}
利用这种思路也能写出递归代码
class Solution {
private TreeNode pre;
public TreeNode insertIntoBST(TreeNode root, int val) {
if(root == null) {
return new TreeNode(val);
}
travel(root,val);
if(pre != null &&pre.val > val) pre.left = new TreeNode(val);
if(pre != null &&pre.val < val) pre.right = new TreeNode(val);
return root;
}
public void travel(TreeNode root,int val){
if(root == null) return;
pre = root;
if(root.val>val) travel(root.left,val);
if(root.val<val) travel(root.right,val);
}
}
在题解中将我们的思路阐述的更加清楚:
其实可以不考虑题目中提示所说的改变树的结构的插入方式。
如下演示视频中可以看出:只要按照二叉搜索树的规则去遍历,遇到空节点就插入节点就可以了。
由于是二叉搜索树,所以遇到的第一个空节点一定是要插入的地方。
题解递归代码:
还是比自己写的要简便很多
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) // 如果当前节点为空,也就意味着val找到了合适的位置,此时创建节点直接返回。
return new TreeNode(val);
if (root.val < val){
root.right = insertIntoBST(root.right, val); // 递归创建右子树
}else if (root.val > val){
root.left = insertIntoBST(root.left, val); // 递归创建左子树
}
return root;
}
}
迭代法与题解相同
删除二叉搜索树中的节点
这道题判断的情况还是有点多的,而且顺序也不能搞错
要先判断 val == key
的情况再进行左右的遍历
其中 val == key
又存在好几种情况,二刷可以看视频
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if(root == null) return root;
if(root.val == key){
//val == key
if(root.left == null && root.right == null) return null;
if(root.left == null && root.right !=null) return root.right;
if(root.left !=null && root.right == null) return root.left;
//root.left!=null && root.right!=null
TreeNode cur = root.right;
while(cur.left !=null){
cur = cur.left;
}
cur.left = root.left;
return root.right;
}
if(root.val > key) root.left = deleteNode(root.left,key);
if(root.val < key) root.right = deleteNode(root.right,key);
return root;
}
}
因为二叉搜索树添加节点只需要在叶子上添加就可以的,不涉及到结构的调整,而删除节点操作涉及到结构的调整。
这里我们依然使用递归函数的返回值来完成把节点从二叉树中移除的操作。
这里最关键的逻辑就是第五种情况(删除一个左右孩子都不为空的节点),这种情况一定要想清楚。
而且就算想清楚了,对应的代码也未必可以写出来,所以这道题目既考察思维逻辑,也考察代码能力。