- 二叉搜索树(BST):二叉搜索树是一个有序树
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉搜索树
求二叉搜索树的属性
700. 二叉搜索树中的搜索
-
题目描述
-
题解
/** 递归 */ class Solution { public TreeNode searchBST(TreeNode root, int val) { if(root==null || root.val==val) return root; if(root.val>val){ return searchBST(root.left,val); }else{ return searchBST(root.right,val); } } } -
题解2:迭代法
一提到二叉树遍历的迭代法,可能立刻想起使用栈来模拟深度遍历,使用队列来模拟广度遍历
对于二叉搜索树可就不一样了,因为二叉搜索树的特殊性,也就是节点的有序性,可以不使用辅助栈或者队列就可以写出迭代法
对于一般二叉树,递归过程中还有回溯的过程,例如走一个左方向的分支走到头了,那么要调头,在走右分支;而对于二叉搜索树,不需要回溯的过程,因为节点的有序性就帮我们确定了搜索的方向
/** 迭代法 */ class Solution { public TreeNode searchBST(TreeNode root, int val) { while(root!=null){ if(root.val>val){ root=root.left; }else if(root.val<val){ root=root.right; }else{ return root; } } return null; } }
98. 验证二叉搜索树
-
题目描述
-
题解
要知道中序遍历下,输出的二叉搜索树节点的数值是有序序列;有了这个特性,验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了
/** 递归 */ class Solution { long pre=Long.MIN_VALUE; public boolean isValidBST(TreeNode root) { if(root==null) return true; //左 boolean left=isValidBST(root.left); //根 if(pre<root.val){ pre=root.val; }else{ return false; } //右 boolean right=isValidBST(root.right); return left && right; } }/** 迭代 模拟中序遍历 */ class Solution { public boolean isValidBST(TreeNode root) { if(root==null) return true; Stack<TreeNode> stack=new Stack<>(); TreeNode cur=root; TreeNode pre=null; while(!stack.isEmpty() || cur!=null){ //左 if(cur!=null){ stack.push(cur); cur=cur.left; }else{ //根 cur=stack.pop(); if(pre!=null && cur.val<=pre.val) return false; pre=cur; //右 cur=cur.right; } } return true; } }
530. 二叉搜索树的最小绝对差
-
题目描述
-
题解
/** 递归 */ class Solution { //记录下前一个节点,利用二叉搜索树的有序性 TreeNode pre; Integer res=Integer.MAX_VALUE; public int getMinimumDifference(TreeNode root) { if(root==null){ return 0; } traversal(root); return res; } public void traversal(TreeNode root){ if(root==null) return; //左 traversal(root.left); //根 if(pre!=null){ res=Math.min(res,root.val-pre.val); } pre=root; //右 traversal(root.right); } }/** 迭代法 中序遍历 */ class Solution { public int getMinimumDifference(TreeNode root) { Integer res=Integer.MAX_VALUE; if(root==null) return 0; Stack<TreeNode> stack=new Stack<>(); TreeNode cur=root; TreeNode pre=null; while(cur!=null || !stack.isEmpty()){ if(cur!=null){ //将访问的节点入栈 stack.push(cur); //左 cur=cur.left; }else{ //根 cur=stack.pop(); if(pre!=null){ res=Math.min(res,cur.val-pre.val); } pre=cur; //右 cur=cur.right; } } return res; } }
501. 二叉搜索树中的众数
-
题目描述
-
题解
/** 递归 */ class Solution { List<Integer> result; //最大频次 Integer maxCount; //统计频次 Integer count; //记录前一个节点 TreeNode pre=null; public int[] findMode(TreeNode root) { result=new ArrayList<>(); if(root==null) return null; maxCount=0; count=0; traversal(root); int[] res=new int[result.size()]; for(int i=0;i<result.size();i++){ res[i]=result.get(i); } return res; } public void traversal(TreeNode root){ if(root==null) return; //左 traversal(root.left); //中 pre=root; //第一次计数 if(pre==null || pre.val!=root.val){ count=1; }else{ count++; } //跟新最大频次并清空列表 if(count>maxCount){ maxCount=count; result.clear(); result.add(root.val); }else if(count==maxCount){ result.add(root.val); } //右 traversal(root.right); } }/** 迭代法 */ class Solution { public int[] findMode(TreeNode root) { if(root==null) return null; //记录前一个节点 TreeNode pre=null; TreeNode cur=root; List<Integer> result=new ArrayList<>(); Stack<TreeNode> stack=new Stack<>(); Integer maxCount=0; Integer count=0; while(cur!=null || !stack.isEmpty()){ //左 if(cur!=null){ stack.push(cur); cur=cur.left; }else{ //根 cur=stack.pop(); if(pre==null || cur.val!=pre.val){ count=1; }else{ count++; } //更新最大频次并清空队列 if(count>maxCount){ maxCount=count; result.clear(); result.add(cur.val); }else if(count==maxCount){ result.add(cur.val); } pre=cur; //右 cur=cur.right; } } int[] res=new int[result.size()]; for(int i=0;i<result.size();i++){ res[i]=result.get(i); } return res; } }
二叉树的公共祖先问题
236. 二叉树的最近公共祖先
-
题目描述
-
题解
遇到这个题目首先想的是要是能自底向上查找就好了,这样就可以找到公共祖先了
那么二叉树如何可以自底向上查找呢?回溯啊,二叉树回溯的过程就是从低到上
-
首先最容易想到的一个情况:如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先
-
但是很多人容易忽略一个情况,就是节点本身p(q),它拥有一个子孙节点q(p);使用后序遍历,回溯的过程,就是从低向上遍历节点,一旦发现满足第一种情况的节点,就是最近公共节点了
但是如果p或者q本身就是最近公共祖先呢?其实只需要找到一个节点是p或者q的时候,直接返回当前节点,无需继续递归子树。如果接下来的遍历中找到了后继节点满足第一种情况则修改返回值为后继节点,否则,继续返回已找到的节点即可
结果是如何一层一层传上去的:
- 求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从低向上的遍历方式
- 在回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断
- 要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果
/** 递归 */ class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { //递归结束条件 if(root==null || p==root || q==root) return root; //后序遍历 TreeNode left=lowestCommonAncestor(root.left,p,q); TreeNode right=lowestCommonAncestor(root.right,p,q); //找到两个节点 if(left!=null && right!=null) return root; else if(left==null && right!=null) return right; else if(left!=null && right==null) return left; //未找到节点 else return null; } } -
235. 二叉搜索树的最近公共祖先
-
题目描述
-
题解
二叉搜索树是有序的,那得好好利用一下这个特点。
在有序树里,如果判断一个节点的左子树里有p,右子树里有q呢?
其实只要从上到下遍历的时候,cur节点是数值在[p, q]区间中则说明该节点cur就是最近公共祖先了
/** 递归 */ class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { if(root==null) return root; if(root.val>p.val && root.val>q.val){ return lowestCommonAncestor(root.left,p,q); }else if(root.val<p.val && root.val<q.val){ return lowestCommonAncestor(root.right,p,q); }else return root; } }/** 迭代法 */ 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{ break; } } return root; } }
二叉搜索树的修改与改造
701. 二叉搜索树中的插入操作
-
题目描述
-
题解
只要按照二叉搜索树的规则去遍历,遇到空节点就插入节点就可以了
/** 递归 */ 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; } }/** 二叉搜索树的有序特性,不需要遍历全部节点 迭代法 找到空节点 并根据记录父节点插入 */ 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; } } TreeNode addNode=new TreeNode(val); if(pre.val>val){ pre.left=addNode; }else{ pre.right=addNode; } return root; } }
450. 删除二叉搜索树中的节点
-
题目描述
-
题解
有以下五种情况:
-
第一种情况:没找到删除的节点,遍历到空节点直接返回了
-
找到删除的节点
-
第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
-
第三种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
-
第四种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
-
第五种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点
第五种情况有点难以理解,看下面动画:
动画中棵二叉搜索树中,删除元素7, 那么删除节点(元素7)的左孩子就是5,删除节点(元素7)的右子树的最左面节点是元素8。
将删除节点(元素7)的左孩子放到删除节点(元素7)的右子树的最左面节点(元素8)的左孩子上,就是把5为根节点的子树移到了8的左孩子的位置。
要删除的节点(元素7)的右孩子(元素9)为新的根节点
-
/** 递归 */ class Solution { public TreeNode deleteNode(TreeNode root, int key) { //第一种情况:没找到删除的节点,遍历到空节点直接返回了 if(root==null) return null; if(root.val>key){ root.left=deleteNode(root.left,key); }else if(root.val<key){ root.right=deleteNode(root.right,key); }else{ //找到要删除的节点 //第三种情况:其左孩子为空,右孩子不为空,删除节点,右孩子补位 ,返回右孩子为根节点 if(root.left==null) return root.right; //第四种情况:其右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点 if(root.right==null) return root.left; //第五种情况:左右孩子节点都不为空 //则将删除节点的左子树放到删除节点的右子树的最左面节点的左孩子的位置 TreeNode tmp=root.right; while(tmp.left!=null){ tmp=tmp.left; } tmp.left=root.left; //并返回删除节点右孩子为新的根节点 root=root.right; } return root; } } -
669. 修剪二叉搜索树
-
题目描述
-
题解
如果root(当前节点)的元素小于low的数值,那么应该递归右子树,并返回右子树符合条件的头结点
如果root(当前节点)的元素大于high的,那么应该递归左子树,并返回左子树符合条件的头结点
接下来要将下一层处理完左子树的结果赋给root->left,处理完右子树的结果赋给root->right
/** 递归 */ class Solution { public TreeNode trimBST(TreeNode root, int low, int high) { if(root==null) return null; if(root.val<low) return trimBST(root.right,low,high); if(root.val>high) return trimBST(root.left,low,high); root.left=trimBST(root.left,low,high); root.right=trimBST(root.right,low,high); return root; } }此时大家是不是还没发现这多余的节点究竟是如何从二叉树中移除的呢?
在回顾一下上面的代码,针对下图中二叉树的情况:
如下代码相当于把节点0的右孩子(节点2)返回给上一层
if(root.val<low) return trimBST(root.right,low,high);然后如下代码相当于用节点3的左孩子 把下一层返回的 节点0的右孩子(节点2) 接住。
root.left=trimBST(root.left,low,high);此时节点3的左孩子就变成了节点2,将节点0从二叉树中移除了
-
题解2:迭代法
因为二叉搜索树的有序性,不需要使用栈模拟递归的过程,在剪枝的时候,可以分为三步:
- 将root移动到[L, R] 范围内,注意是左闭右闭区间
- 剪枝左子树
- 剪枝右子树
class Solution { public TreeNode trimBST(TreeNode root, int low, int high) { if(root==null) return null; //处理头结点,让root移动到[L, R] 范围内,注意是左闭右闭 while(root!=null && (root.val<low || root.val>high)){ //小于low往右走 if(root.val<low){ root=root.right; }else{ //大于high往左走 root=root.left; } } //此时root已经在[L, R] 范围内,处理左孩子元素小于L的情况 TreeNode cur=root; while(cur!=null){ while(cur.left!=null && cur.left.val<low){ cur.left=cur.left.right; } cur=cur.left; } //此时root已经在[L, R] 范围内,处理右孩子大于R的情况 cur=root; while(cur!=null){ while(cur.right!=null && cur.right.val>high){ cur.right=cur.right.left; } cur=cur.right; } return root; } }
108. 将有序数组转换为二叉搜索树
-
题目描述
-
题解
本质就是寻找分割点,分割点作为当前节点,然后递归左区间和右区间。
因为有序数组构造二叉搜索树,寻找分割点就比较容易了,分割点就是数组中间位置的节点
那么为问题来了,如果数组长度为偶数,中间节点有两个,取哪一个?
取哪一个都可以,只不过构成了不同的平衡二叉搜索树
class Solution { public TreeNode sortedArrayToBST(int[] nums) { return traversal(nums,0,nums.length-1); } public TreeNode traversal(int[] nums,Integer start,Integer end){ if(start>end) return null; int mid=start+(end-start)/2; TreeNode res=new TreeNode(nums[mid]); res.left=traversal(nums,start,mid-1); res.right=traversal(nums,mid+1,end); return res; } }
538. 把二叉搜索树转换为累加树
-
题目描述
-
题解
其实这就是一棵树,大家可能看起来有点别扭,换一个角度来看,这就是一个有序数组[2, 5, 13],求从后到前的累加数组,也就是[20, 18, 13],是不是感觉这就简单了
为什么变成数组就是感觉简单了呢?因为数组大家都知道怎么遍历啊,从后向前,挨个累加就完事了,这换成了二叉搜索树,看起来就别扭了一些是不是
那么知道如何遍历这个二叉树,也就迎刃而解了,从树中可以看出累加的顺序是右中左,所以我们需要反中序遍历这个二叉树,然后顺序累加就可以了
class Solution { //记录前一个节点的值 int pre=0; public TreeNode convertBST(TreeNode root) { traversal(root); return root; } public void traversal(TreeNode root){ if(root==null) return; //右 traversal(root.right); //根 累加并且记录前一个节点的值 root.val+=pre; pre=root.val; //左 traversal(root.left); } }/** 迭代 注意跟二叉搜索树顺序不同 反中序遍历 :右根左 */ class Solution { public TreeNode convertBST(TreeNode root) { if(root==null) return null; int pre=0; TreeNode cur=root; Stack<TreeNode> stack=new Stack<>(); while(cur!=null || !stack.isEmpty()){ if(cur!=null){ //右 stack.push(cur); cur=cur.right; }else{ //根 cur=stack.pop(); cur.val+=pre; pre=cur.val; //左 cur=cur.left; } } return root; } }