题目:
给定二叉搜索树(BST)的根节点 root 和一个整数值 val。
你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。
写题历程:
这道题只要抓住二叉搜索树的特性就很好做了,左边比根节点值小,右边比根节点值大,剩下的搜索,只是一些判断,然后递归搜索就行了
解法:
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
if(root == null) return null;
if(root.val == val) return root;
if(root.val > val) {
return searchBST(root.left,val);
}
if(root.val < val) {
return searchBST(root.right,val);
}
return null;
}
}
题目:
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
节点的左子树只包含 小于 当前节点的数。
节点的右子树只包含 大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
写题历程:
一开始掉坑里了,只比较了左节点和右节点与当前节点的情况,但是!
二叉搜索树不是只对左右节点做要求呀,而是左右子树,是子树
或许左右节点确实满足条件了,但是左右节点的子节点呢,怕是不一定吧
在我意识到这个之后(其实是看题解才发现我掉坑了),思考怎么整...
我们可以用一个变量去记录当前节点值,采用中序遍历的方式,先递归左子树,若当前节点值大于左结点值,记录当前节点值,再去递归右子树,最后左右子树相与返回
解法:
class Solution {
Long maxVal = -2147483649L;
public boolean isValidBST(TreeNode root) {
if(root == null) return true;
boolean leftTree = isValidBST(root.left);
if(!leftTree) return false;
if(root.val <= maxVal) return false;
maxVal = Long.valueOf(root.val);
boolean rightTree = isValidBST(root.right);
return leftTree && rightTree;
}
}
题目:
给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
写题历程:
利用二叉搜索树的特性,根据中序遍历,使之有序,再来查找最小绝对差值
如何找最小绝对差值?第一,用一个变量记录答案,第二,用一个变量记录当前节点的上一节点的值,这样我们就可以计算差值,将差值更新到答案上,直至找到最小
解法:
class Solution {
Integer res = Integer.MAX_VALUE;
Integer pre = null; //记录前一个节点的值
public int getMinimumDifference(TreeNode root) {
if(root == null) return 0;
int left = getMinimumDifference(root.left);
if(pre != null) {
res = Math.min(res,root.val - pre);
}
pre = root.val;
int right = getMinimumDifference(root.right);
return res;
}
}
题目:
给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。
假定 BST 满足如下定义:
1. 结点左子树中所含节点的值 小于等于 当前节点的值
2. 结点右子树中所含节点的值 大于等于 当前节点的值
3. 左子树和右子树都是二叉搜索树
写题历程:
二叉搜索树的重要性质:中序遍历后是一个非递减序列
因此问题简化成,在一个有序非递减数组中找出现频率最高的数
但是写的过程中,代码还是差点意思,考虑的情况不周
不仅要比较当前节点与上一个节点的值,还需要找到最高频率
通过多重判断,最后将集合转换成数组。思路上确实不难,但实现起来步骤还是有些许繁琐的
解法:
class Solution {
List list = new ArrayList<>();
Integer pre = null; //记录上一个节点的值
int maxFrequency = 0; //最大频率
int frequency = 0; //当前节点的出现频率
public int[] findMode(TreeNode root) {
searchMode(root);
return list.stream().mapToInt(Integer::intValue).toArray();
}
public void searchMode(TreeNode node) {
if(node == null) return;
searchMode(node.left);
if(pre != null && pre == node.val) frequency ++;
else frequency = 1; //如果不相同,频率只能为1,因为只出现一次
if(frequency > maxFrequency) {
maxFrequency = frequency; //更新最大频率
list.clear(); //加入集合的元素应该移除,因为有频率更高的数了
list.add(node.val);
} else if(frequency == maxFrequency) {
list.add(node.val);
}
pre = node.val;
searchMode(node.right);
}
}
题目:
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
写题历程:
利用好搜索树的性质,左边小,右边大
那就可以比较根节点与左右子树节点的大小关系
均大于,递归左边;均小于,递归右边
在之间,则返回
解法:
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//经过中序遍历可以将二叉搜索树变成一个有序数组,找两个节点的最近公共祖先
//相当于在数组中[p,q]可以找到该最近公共祖先
//如何在数组中准确找到?如何排除其他元素?
if(root == null) return null;
if(root.val > p.val && root.val > q.val) {
return lowestCommonAncestor(root.left,p,q);
}
if(root.val < p.val && root.val < q.val) {
return lowestCommonAncestor(root.right,p,q);
}
return root;
}
}
题目:
给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
写题历程:
有一说一,这题我思路来的挺快的,只是对于回溯的领悟还有点差距,回溯条件想错了
看了点提示后,再自己人工debug了下,弄清楚了
这种带有性质的树其实反而不用那么担心,正是因为特殊,而恰好能利用它的性质去解决
例如本题插入节点,我们只需判断下当前节点的值与要插入的值的大小关系,当前节点值大,则插到左边,反之右边,然后递归,遇到空就回溯,然后直接插入就行了
解法:
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
return traversal(root,val);
}
public TreeNode traversal(TreeNode root, int val) {
if(root == null) return new TreeNode(val); //如果是空树,直接用插入的节点创建一棵树
if(root.val > val) {
TreeNode left = traversal(root.left,val);
root.left = left; //回溯
}
if(root.val < val) {
TreeNode right = traversal(root.right,val);
root.right = right; //回溯
}
return root;
}
}
题目:
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点;
如果找到了,删除它。
写题历程:
我本以为这题和在二叉搜索树中插入节点的思路差不多,没想到它需要考虑的情况这么多
首先最主要的是,删除节点会修改二叉搜索树的结构,即你删除完节点后,还需要调整为一颗二叉搜索树
分五种情况:
未找到目标节点,即根节点为空
找到目标节点
目标节点是叶子节点
目标节点的左子树为空,右子树不为空
目标节点的左子树不为空,右子树为空
目标节点的左右子树均不为空
具体理一下 目标节点的左右子树均不为空 的思路:
当目标节点的左右子树均不为空,且需要删除目标节点,首先目标节点的父节点必定要连接目标节点的子节点
其次,连接完毕后,还得保证这棵树仍然是一棵二叉搜索树
我们可以把目标节点的左右子树进行合并,再调整结构,最后与目标节点的父节点进行连接
具体实现:(将左子树合并到右子树)
利用一个临时节点指向目标节点的右子节点
遍历这棵子树找到此树的最左叶子节点
最左叶子节点去连接目标节点的左子树,即完成合并
最后一步,将目标节点删除,即目标节点的右节点成为新的根节点
当然,将右子树合并到左子树也是ok的,把里面左右修改下就行,答案不唯一
解法:
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if(root == null) return root; //第一种情况
if(root.val == key) { //找到目标
if(root.left == null) return root.right; //这两行包含了二三四的情况
else if(root.right == null) return root.left;
else { //第五种情况
TreeNode cur = root.right;
while(cur.left != null) {
cur = cur.left;
}
cur.left = root.left;
root = root.right;
return root;
}
}
if(root.val > key) root.left = deleteNode(root.left,key);
if(root.val < key) root.right = deleteNode(root.right,key);
return root;
}
}
题目:
给你二叉搜索树的根节点 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;
}
}
/**
后序
**/
class Solution {
public TreeNode trimBST(TreeNode root, int low, int high) {
if(root == null) return root;
root.left = trimBST(root.left,low,high);
root.right = trimBST(root.right,low,high);
//不满足条件
if(root.val < low || root.val > high) {
if(root.left == null) return root.right;
else if(root.right == null) return root.left;
}
return root;
}
}
题目:
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
写题历程:
前面反复提过,二叉搜索树的一个重要性质:中序遍历将变成一个非递减序列
而这一题不就恰好反过来而已吗
我们可以根据上述性质知道,数组中间的元素就是头节点,然后以此分割,左边的在左子树,右边的在右子树,递归一下即可
还有注意分割的时候,要遵循 循环不变量原则
解法:
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return traversal(nums,0,nums.length);
}
public TreeNode traversal(int[] nums, int start,int end) {
if(start == end) return null;
int index = start + end >> 1;
TreeNode root = new TreeNode(nums[index]);
if(root == null) return root;
//左闭右开
root.left = traversal(nums,start,index);
root.right = traversal(nums,index + 1, end);
return root;
}
}
题目:
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
节点的左子树仅包含键 小于 节点键的节点。
节点的右子树仅包含键 大于 节点键的节点。
左右子树也必须是二叉搜索树。
写题历程:
一开始没看懂题,小小地绕了一下,不过可以看出它有点像是反过来的二叉搜索树(个人的理解),即根节点的右边小,左边大
照着这个思路,再根据二叉搜索树的重要性质,可以知道它的中序遍历也是反的,也就是右中左,再就是累加树。至此,题目算是理清了
我们从从右到左递归遍历再累加,最后返回根节点,就可以搞定这题了
具体看代码...
解法:
class Solution {
Integer sum = 0; //累加器
public TreeNode convertBST(TreeNode root) {
convert(root);
return root;
}
public void convert(TreeNode root) {
if(root == null) return ;
convert(root.right); //右
//中
sum += root.val; //每多遍历一个节点,则多累加一个节点的值
root.val = sum; //将当前节点的值更新为累加值
convert(root.left); //左
}
}