● 530.二叉搜索树的最小绝对差
给你一个二叉搜索树的根节点 root
,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
思路
与98. 类似,可以利用中序遍历,将二叉搜索树转换为一个 递增的数组,然后遍历数组即可。
但其实,在中序遍历的过程中,就可以求解。
递归:利用pre记录前一个节点,然后用root(当前节点) 的值减去pre的值,更新result
迭代:中序遍历的迭代写法;同样需要用pre记录前一个节点。
递归
class Solution {
TreeNode pre = null;
int result = Integer.MAX_VALUE;
public int getMinimumDifference(TreeNode root) {
if(root == null) return 0;
getMin(root);
return result;
}
public void getMin(TreeNode root){
if(root == null) return;
getMin(root.left);
if(pre != null){
result = Math.min(result, root.val - pre.val);
}
pre = root;
getMin(root.right);
}
}
迭代
class Solution {
TreeNode pre = null;
public int getMinimumDifference(TreeNode root) {
if(root == null) return 0;
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode cur = root;
int result = Integer.MAX_VALUE;
while(!stack.isEmpty() || cur != null){
if(cur != null){
stack.push(cur);
cur = cur.left;
}else{ //此时无左节点,栈顶为最左下节点,cur为null
cur = stack.pop();
if(pre != null){
result = Math.min(result, cur.val - pre.val);
}
pre = cur;
cur = cur.right;
}
}
return result;
}
}
总结
解题思维是一样的,区别在于遍历的方式而已。
都需要利用pre记录前一个节点,注意下添加pre的写法。
● 501.二叉搜索树中的众数
给你一个含重复值的二叉搜索树(BST)的根节点 root
,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。
假定 BST 满足如下定义:
- 结点左子树中所含节点的值 小于等于 当前节点的值
- 结点右子树中所含节点的值 大于等于 当前节点的值
- 左子树和右子树都是二叉搜索树
思路
1、最直观的思路,遍历二叉树,利用map记录元素 与 出现频率,然后返回频率最高的元素集合
此时可以选择任意遍历方式。具体代码思路如下
1、遍历二叉树,map记录频率
2、对频率(map的value)排序
3、取最高频元素
2、上述思路没有利用搜索树的性质,因为是二叉搜索树,所以中序遍历元素是有序的,在统计元素频率的时候,从头遍历,那么一定是相邻两个元素作比较,然后就把出现频率最高的元素输出就可以了。
在使用中序遍历方法的时候,注意pre的使用:
pre指针指向前一个节点,这样每次cur(当前节点)才能和pre(前一个节点)作比较。
而且初始化的时候pre = NULL,这样当pre为NULL时候,我们就知道这是比较的第一个元素。
代码如下:
//计数
if(pre == null || pre.val != root.val){
count = 1;
}else{
count++;
}
pre = root;
然后是如何得到频率最高的元素:
因为要求最大频率的元素集合(注意是集合,不是一个元素,可以有多个众数),如果是数组上大家一般怎么办?应该是先遍历一遍数组,找出最大频率(maxCount),然后再重新遍历一遍数组把出现频率为maxCount的元素放进集合。(因为众数有多个)
这种方式遍历了两遍数组。那么我们遍历两遍二叉搜索树,把众数集合算出来也是可以的。但这里其实只需要遍历一次就可以找到所有的众数。
那么如何只遍历一遍呢?
如果 频率count 等于 maxCount(最大频率),当然要把这个元素加入到结果集中(以下代码为result数组),代码如下:
if(count == maxCount){
resList.add(root.val);
}
这个maxCount此时还不是真正最大频率,所以下面要做如下操作:
频率count 大于 maxCount的时候,不仅要更新maxCount,而且要清空结果集(以下代码为result数组),因为结果集之前的元素都失效了。
if(count > maxCount){
resList.clear();
maxCount = count;
resList.add(root.val);
}
由此,所有关键逻辑完成,代码如下
递归
class Solution {
TreeNode pre;
int count;
int maxCount;
ArrayList<Integer> resList;
public int[] findMode(TreeNode root) {
resList = new ArrayList<>();
count = 0;
maxCount = 0;
pre = null;
find(root);
int[] res = new int[resList.size()];
for (int i = 0; i < resList.size(); i++) {
res[i] = resList.get(i);
}
return res;
}
public void find(TreeNode root){
if(root == null) return;
find(root.left);
//计数
if(pre == null || pre.val != root.val){
count = 1;
}else{
count++;
}
pre = root;
//更新结果,注意此处处理使得不需要遍历第二次
if(count > maxCount){
resList.clear();
maxCount = count;
resList.add(root.val);
}else if(count == maxCount){
resList.add(root.val);
}
find(root.right);
}
}
迭代
迭代法与递归法思路完全相同,只是遍历的方式不一样
class Solution {
public int[] findMode(TreeNode root) {
TreeNode pre = null;
Stack<TreeNode> stack = new Stack<>();
List<Integer> result = new ArrayList<>();
int maxCount = 0;
int count = 0;
TreeNode cur = root;
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;
}
}
return result.stream().mapToInt(Integer::intValue).toArray();
}
}
暴力(适用于非二叉搜索树)
class Solution {
public int[] findMode(TreeNode root) {
Map<Integer, Integer> map = new HashMap<>();
List<Integer> list = new ArrayList<>();
if (root == null) return list.stream().mapToInt(Integer::intValue).toArray();
// 获得频率 Map
searchBST(root, map);
List<Map.Entry<Integer, Integer>> mapList = map.entrySet().stream()
.sorted((c1, c2) -> c2.getValue().compareTo(c1.getValue()))
.collect(Collectors.toList());
list.add(mapList.get(0).getKey());
// 把频率最高的加入 list
for (int i = 1; i < mapList.size(); i++) {
if (mapList.get(i).getValue() == mapList.get(i - 1).getValue()) {
list.add(mapList.get(i).getKey());
} else {
break;
}
}
return list.stream().mapToInt(Integer::intValue).toArray();
}
void searchBST(TreeNode curr, Map<Integer, Integer> map) {
if (curr == null) return;
map.put(curr.val, map.getOrDefault(curr.val, 0) + 1);
searchBST(curr.left, map);
searchBST(curr.right, map);
}
}
● 236. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
思路
自底向上-回溯-后序遍历
递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:
如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii)
如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (本题情况)
如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(lc112的情况)
如何处理返回值:
如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树呢?
搜索一条边的写法:
if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;
搜索整棵树的写法:
left = 递归函数(root->left); // 左
right = 递归函数(root->right); // 右
left与right的逻辑处理; // 中
结论:在递归函数有返回值的情况下:如果要搜索一条边,递归函数返回值不为空的时候,立刻返回,如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)。
递归
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
return find(root, p, q);
}
public TreeNode find(TreeNode root, TreeNode p, TreeNode q){
if(root == q || root == p || root == null) return root;
TreeNode left = find(root.left, p, q);
TreeNode right = find(root.right, p, q);
if(left == null && right == null) return null;
else if(left != null && right == null) return left;
else if(left == null && right != null) return right;
else return root;
}
}