文章目录
669.修剪二叉搜索树
题目描述
给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树不应该改变保留在树中的元素的相对结构(即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在唯一的答案。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。
解题思路
如果根节点的值比给定的右边界还大,由二叉搜索树的性质可知,右子树的所有值也都是比给定的右边界大的,那么就在左子树中进行修剪即可。
同理,如果根节点的值比给定的左边界还小,左子树的所有值也都是比给定的左边界小的值,那么只用在右子树中进行修剪即可。
如果根节点的值在给定的左边界和右边界之间,那么就采用递归分别在左右子树中进行修剪作为左右子树。
代码实现
class Solution {
public TreeNode trimBST(TreeNode root, int low, int high) {
if(root==null){
return root;
}
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;
}
}
230.二叉搜索树的第k小元素
题目大意
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。
解题思路
(一)递归实现:
- 二叉搜索树的中序遍历是有序的,第k小元素就是中序遍历的k-1个元素。
时间复杂度:O(N),遍历整棵树
空间复杂度:O(N),利用数组来存储中序遍历序列。 - 不将中序遍历的结果保存到集合中了,而是根据计数,到达访问中序遍历的第k个元素的时候,就将其返回。
- 计算左子树中节点的个数,如果左子树中节点的个数为
k-1
个,那么整棵树的根节点就是第k
小的元素;如果左子树的节点个数大于k-1
那么第k
小元素就在左子树中递归来找;反之,如果小于k-1
,那么就去右子树中去找。
(二)迭代实现
在栈的帮助下,可以将方法一的递归转化为迭代,这样可以加快速度,不用遍历整棵树了,只要找到第k小值就结束遍历。
一直向左寻找左节点加入到栈中,每将一个值退出栈就将k–,当k减到0时就表示找到了第k小的元素。
栈的创建:
LinkedList<TreeNode> stack=new LinkedList<>();
代码实现
(一)递归解法1.0
class Solution {
public int kthSmallest(TreeNode root, int k) {
//二叉搜索树的中序遍历是有序的,第k小元素就是中序遍历的第k-1个元素
List<Integer> res=new ArrayList<>();
if(root==null){
return 0;
}
inorder(root,res);
return res.get(k-1);
}
public void inorder(TreeNode root,List<Integer> res){
if(root==null){
return ;
}
inorder(root.left,res);
res.add(root.val);
inorder(root.right,res);
}
}
递归解法2.0:去除了存放结果的集合,而是不保存中序遍历的结果直接进行返回第k小值。
private int cnt = 0;
private int val;
public int kthSmallest(TreeNode root, int k) {
inOrder(root, k);
return val;
}
private void inOrder(TreeNode node, int k) {
if (node == null) return;
inOrder(node.left, k);
cnt++;
if (cnt == k) {
val = node.val;
return;
}
inOrder(node.right, k);
}
递归解法3.0
class Solution {
public int kthSmallest(TreeNode root, int k) {
int leftCnt = count(root.left);
//如果左子树中的节点个数等于k-1,那么根节点就是第k小的节点
if (leftCnt == k - 1) return root.val;
//如果个数大于k-1,那么递归在左子树中找第k小值
if (leftCnt > k - 1) return kthSmallest(root.left, k);
//反之,就在右子树中找,注意个数要减去左子树的节点数
return kthSmallest(root.right, k - leftCnt - 1);
}
//计算以node为根的树中一共有多少个节点 利用递归来实现
private int count(TreeNode node) {
if (node == null) return 0;
return 1 + count(node.left) + count(node.right);
}
}
(二)迭代实现
class Solution {
public int kthSmallest(TreeNode root, int k) {
//二叉搜索树的中序遍历是有序的,第k小元素就是中序遍历的第k-1个元素
//利用迭代来实现
LinkedList<TreeNode> stack=new LinkedList<>();
while(true){
while(root!=null){
stack.add(root);
root=root.left;
}
TreeNode node=stack.removeLast();
if(--k==0){
return node.val;
}
root=node.right;
}
}
}
538. 把二叉搜索树转换为累加树
题目大意
给出二叉搜索树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
节点的左子树仅包含键 小于 节点键的节点。
节点的右子树仅包含键 大于 节点键的节点。
左右子树也必须是二叉搜索树。
解题思路
==二叉树的反向中序遍历。==二叉树搜索树的中序遍历结果是从小到大,中序遍历的反向就是从大到小。大于等于节点的值存在于根节点本身以及右子树中。
**注意代码中定义的变量sum
是一路累加的。
代码实现
class Solution {
//sum一路累加
public int sum=0;
public TreeNode convertBST(TreeNode root) {
//中序遍历的反遍历
if(root!=null){
convertBST(root.right);
sum+=root.val;
root.val=sum;
convertBST(root.left);
}
return root;
}
}
235. 二叉搜索树的最近公共祖先
题目大意
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
解题思路
对于二叉搜索树而言,使用递归来解决:
如果根节点的值是大于两个节点的值,那么就递归的在左子树中找两个节点的最近公共祖先;如果根节点的值小于两个节点的值,那么就在右子树中找;如果根节点的值的大小在两个节点之间,那么根节点就是两个节点的最近公共祖先。
代码实现
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.right,p,q);
}
if(root.val>q.val&&root.val>p.val){
return lowestCommonAncestor(root.left,p,q);
}
return root;
}
}
236.二叉树的最近公共祖先
题目大意
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
解题思路
(一)递归来实现
如果其中一个节点为根节点,那么根节点就是最近公共祖先。如果不是,就递归的在左右子树中寻找。如果在左右子树中找到就返回不为空的那个,如果左右子树中都没有找到,就表示两个节点分别在左右子树中,就返回根节点。
(二)存储父节点
可以使用哈希表存储所有节点的父节点,然后我们就可以利用节点的父节点信息从p节点开始不断往上跳,并记录已经访问过的节点,再从q节点不断向上跳,如果碰到已经访问过的节点,那么这个节点就是我们要找的最近公共祖先。
算法:
- 从根节点开始遍历整棵二叉树,用哈希表记录每个节点的父节点指针。
- 从p节点开始不断往他的祖先移动,并用数据结构记录已经访问过的祖先节点。
- 同样,我们再从q节点开始不断往他的祖先的移动,如果有祖先已经被访问过,即意味着这是p和q的深度最深的公共祖先,即LCA节点。
代码实现
(一)递归来实现
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//递归来实现
if(root==null||root==p||root==q){
return root;
}
TreeNode left=lowestCommonAncestor(root.left,p,q);
TreeNode right=lowestCommonAncestor(root.right,p,q);
return left==null?right:right==null?left:root;
}
}
(二)存储父节点
class Solution {
//利用HashMap来存储当前节点的值以及父节点
HashMap<Integer,TreeNode> parent=new HashMap<>();
//利用HashSet来存储已经访问过的节点 存放无序的不可重复的元素
Set<Integer> visited=new HashSet<>();
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//递归来实现遍历整棵树 将节点值以及父节点放入到哈希表中
dfs(root);
while(p!=null){
//一直找p的父节点,并标记已经访问过
visited.add(p.val);
p=parent.get(p.val);
}
while(q!=null){
//找q的父节点,并判断是否被访问过,如果被访问过则该点就是最近的公共祖先
if(visited.contains(q.val)){
return q;
}
q=parent.get(q.val);
}
return null;
}
public void dfs(TreeNode root){
if(root.left!=null){
parent.put(root.left.val,root);
dfs(root.left);
}
if(root.right!=null){
parent.put(root.right.val,root);
dfs(root.right);
}
}
}
108.将有序数组转换为二叉搜索树
题目大意
给你一个整数数组 nums ,其中元素已经按升序排列,请你将其转换为一棵高度平衡二叉搜索树。
高度平衡二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
解题思路
找出中间节点,作为二叉搜索树的根,然后递归的建立左右子树。
中间节点的确定方式:
int m=l+(r-l+1)/2;
代码实现
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
TreeNode root=nums==null?null:buildTree(nums,0,nums.length-1);
return root;
}
public TreeNode buildTree(int[] nums,int l,int r){
if(l>r) return null;
int m=l+(r-l+1)/2;
TreeNode root=new TreeNode(nums[m]);
root.left=buildTree(nums,l,m-1);
root.right=buildTree(nums,m+1,r);
return root;
}
}
109.有序链表转换为二叉搜索树
题目大意
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
解题思路
(一)分治法:
设当前链表的左端点为left,右端点为right,包含关系为<左闭右开>,也就是left包含在链表中,而right不包含在链表中。为什么这样设置?
由于题目中给定的链表为单向链表,访问后继元素十分容易,但是无法直接访问前驱元素,因此在找到链表的中位数节点mid之后,如果设定左闭右开的关系,就直接可以用(left,mid)以及(mid.next,right)来表示左右子树对应的列表,而且,初始的列表也可以用(head,null)方便的进行表示,其中null表示空节点。
时间复杂度:O(nlogn),其中n是链表的长度。设长度为n的链表构建二叉搜索树的时间为T(n),递推式T(n)=2T(n/2)+O(n),根据主定理,T(n)=O(nlogn)。
空间复杂度:O(logn),这里只计算除了返回答案之外的空间。平衡二叉树的高度为O(logn),即为递归过程中栈的最大深度,也就是需要的空间。
(二)中序遍历+分治:
其实我们已经知道了这棵二叉树的结构了,我们只需要根据给定的中序遍历结果,对其进行中序遍历,就可以还原这棵二叉搜索树了。
时间复杂度:O(n),n是链表的长度。长度为n的链表构造二叉搜索树的时间为T(n),递推式T(n)=2T(n/2)+O(1),根据主定理,T(n)=O(n)。
空间复杂度:O(logn),平衡二叉树的高度为O(logn),即为递归过程中栈的最大深度,也就是需要的空间。
(三)将有序链表转化为有序数组
同上一题的实现过程,只是多了将链表转化为数组这一过程。
代码实现
(一)分治法:
class Solution {
//快慢指针&递归建树
public TreeNode sortedListToBST(ListNode head) {
TreeNode root;
if(head==null){
return null;
}
if(head.next==null){
return root=new TreeNode(head.val);
}
return root=buildTree(head,null);
}
public TreeNode buildTree(ListNode left,ListNode right){
if(left==right){
return null;
}
ListNode mid=find(left,right);
TreeNode root=new TreeNode(mid.val);
root.left=buildTree(left,mid);
root.right=buildTree(mid.next,right);
return root;
}
//利用快慢指针找链表的中间节点
public ListNode find(ListNode l,ListNode r){
ListNode fast=l;
ListNode slow=l;
while(fast!=r&&fast.next!=r){
fast=fast.next.next;
slow=slow.next;
}
return slow;
}
}
(二)中序遍历+分治
class Solution {
//分治+中序遍历
ListNode globalhead;
public TreeNode sortedListToBST(ListNode head) {
if(head==null){
return null;
}
if(head.next==null){
return new TreeNode(head.val);
}
globalhead=head;
int length=countLength(head);
TreeNode root=buildTree(0,length-1);
return root;
}
public int countLength(ListNode head){
int count=0;
ListNode p=head;
while(p!=null){
count++;
p=p.next;
}
return count;
}
public TreeNode buildTree(int l,int r){
if(l>r){
return null;
}
int mid=(l+r+1)/2;
//制造一个空节点
TreeNode root=new TreeNode();
root.left=buildTree(l,mid-1);
root.val=globalhead.val;
globalhead=globalhead.next;
root.right=buildTree(mid+1,r);
return root;
}
}
653.两数之和IV–输入BST
题目大意
给定一个二叉搜索树和一个目标结果,如果 BST 中存在两个元素且它们的和等于给定的目标结果,则返回 true。
解题思路
(一)中序遍历+双指针
中序遍历整棵树将遍历结果保存到一个数组中,然后使用双指针来看有没有两个值的和为给定的target
。
(二)使用HashSet:
当遍历到一个点时,查看set
集合中是否存在k-root.val
的值,如果存在,则返回true
,否则就将节点的值加入到集合中,然后递归的在左右子树中查找。
(三)BFS+HashSet
方法二使用递归,而BFS使用迭代来遍历整棵树,在对树进行广搜的时候进行保存和查找。
注意队列的创建是用LinkedList结构:
Queue<TreeNode> queue=new LinkedList<>();
代码实现
class Solution {
public boolean findTarget(TreeNode root, int k) {
//中序遍历为有序数组+双指针
List<Integer> res=new ArrayList<>();
if(root==null){
return false;
}
inorder(root,res);
//res是将二叉搜索树升序排列的集合
int i=0,j=res.size()-1;
while(i<j){
if(res.get(i)+res.get(j)==k){
return true;
}
if(res.get(i)+res.get(j)<k){
i++;
}
if(res.get(i)+res.get(j)>k){
j--;
}
}
return false;
}
public void inorder(TreeNode root,List<Integer> res){
if(root==null){
return ;
}
inorder(root.left,res);
res.add(root.val);
inorder(root.right,res);
}
}
(二)HashSet来解决
class Solution {
public boolean findTarget(TreeNode root, int k) {
//使用HashSet:存放无序不可重复的元素
if(root==null){
return false;
}
Set<Integer> set=new HashSet<>();
return find(root,k,set);
}
public boolean find(TreeNode root,int k,Set<Integer> set){
if(root==null){
return false;
}
if(set.contains(k-root.val)){
return true;
}
set.add(root.val);
return find(root.left,k,set)||find(root.right,k,set);
}
}
(三)BFS+HashSet
class Solution {
public boolean findTarget(TreeNode root, int k) {
//使用HashSet:存放无序不可重复的元素
if(root==null){
return false;
}
Set<Integer> set=new HashSet<>();
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
if(queue.peek()!=null){
TreeNode node=queue.remove();
if(set.contains(k-node.val)){
return true;
}
set.add(node.val);
queue.add(node.left);
queue.add(node.right);
}else{
queue.remove();
}
}
return false;
}
}
530.二叉搜索树的最小绝对差
题目大意
给你一棵所有节点为非负值的二叉搜索树,请你计算树中任意两节点的差的绝对值的最小值。
解题思路
(一)中序遍历找最小差值
对二叉搜索树进行中序遍历得到从小到大的序列,然后将相邻的元素进行比较查找差最小的两个元素。
(二)是对方法一的优化,不使用list保存中序遍历的结果了,而是在遍历的同时查找最小值,要对中序遍历的前一个节点进行保存。
代码实现
(一)中序遍历找最小差值
class Solution {
public int getMinimumDifference(TreeNode root) {
List<Integer> list=new ArrayList<>();
if(root==null){
return 0;
}
inorder(root,list);
int res=list.get(1)-list.get(0);
for(int i=2;i<list.size();i++){
res=Math.min(res,list.get(i)-list.get(i-1));
}
return res;
}
public void inorder(TreeNode root,List<Integer> list){
if(root==null){
return ;
}
inorder(root.left,list);
list.add(root.val);
inorder(root.right,list);
}
}
(二)不保存中序遍历结果
class Solution {
private int minValue=Integer.MAX_VALUE;
private TreeNode preNode=null;
public int getMinimumDifference(TreeNode root) {
List<Integer> list=new ArrayList<>();
if(root==null){
return 0;
}
inorder(root);
return minValue;
}
public void inorder(TreeNode root){
if(root==null){
return ;
}
inorder(root.left);
if(preNode!=null){
minValue=Math.min(minValue,root.val-preNode.val);
}
preNode=root;
inorder(root.right);
}
}
501.二叉搜索树中的众数
题目大意
给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。
假定 BST 有如下定义:
结点左子树中所含结点的值小于等于当前结点的值
结点右子树中所含结点的值大于等于当前结点的值
左子树和右子树都是二叉搜索树。
解题思路
对二叉搜索树进行中序遍历,在中序遍历的时候对重复节点值进行计数,也要保存节点的前一个值,因为会出现很多值出现的次数相等,所以用一个集合来保存,最后还要将集合中的元素保存到一个数组中进行输出。
代码实现
class Solution {
private int maxCnt=1;
private int cnt=1;
private TreeNode preNode=null;
public int[] findMode(TreeNode root) {
List<Integer> list=new ArrayList<>();
//中序
inorder(root,list);
int[] res=new int[list.size()];
int idx=0;
for(int value:list){
res[idx++]=value;
}
return res;
}
public void inorder(TreeNode root,List<Integer> list){
if(root==null){
return ;
}
inorder(root.left,list);
if(preNode!=null){
if(preNode.val==root.val){
cnt++;
}else{
cnt=1;
}
}
if(cnt>maxCnt){
maxCnt=cnt;
list.clear();
list.add(root.val);
}else if(cnt==maxCnt){
list.add(root.val);
}
preNode=root;
inorder(root.right,list);
}
}