530.二叉搜索树的最小绝对差需要领悟一下二叉树遍历上双指针操作,优先掌握递归
题目链接/文章讲解:代码随想录
视频讲解:二叉搜索树中,需要掌握如何双指针遍历!| LeetCode:530.二叉搜索树的最小绝对差_哔哩哔哩_bilibili
题目中要求在二叉搜索树上任意两节点的差的绝对值的最小值。
注意是二叉搜索树,二叉搜索树可是有序的。
遇到在二叉搜索树上求什么最值啊,差值之类的,就把它想成在一个有序数组上求最值,求差值,这样就简单多了。
那么二叉搜索树采用中序遍历,其实就是一个有序数组。
在一个有序数组上求两个数最小差值,最直观的想法,就是把二叉搜索树转换成有序数组,然后遍历一遍数组,就统计出来最小差值了。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
//直观法
class Solution {
List<Integer> array = new ArrayList<>();
public int getMinimumDifference(TreeNode root) {
traversal(root);
if(array.size() < 2) return 0;
int result = Integer.MAX_VALUE;
for(int i = 1; i < array.size(); i++) { // 统计有序数组的最小差值
result = Math.min(result, array.get(i) - array.get(i-1));
}
return result;
}
public void traversal(TreeNode root){
if(root == null) return;
traversal(root.left);
array.add(root.val);// 将二叉搜索树转换为有序数组
traversal(root.right);
}
}
以上代码是把二叉搜索树转化为有序数组了,其实在二叉搜素树中序遍历的过程中,我们就可以直接计算了。
需要用一个pre节点记录一下cur节点的前一个节点。
有的二叉树题目是让返回二叉树是否符合某一特性或让返回二叉树里的某一个节点的数值,就需要返回值(有的题目不需要遍历整个二叉树,只用遍历某个特定的路径,或搜索某个特定节点,这样就需要返回值立刻返回;本题需要遍历整颗二叉树,用全局变量记录结果,不需要递归函数有返回值)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
TreeNode pre = null;
int result = Integer.MAX_VALUE;
public int getMinimumDifference(TreeNode root) {
traversal(root);
return result;
}
public void traversal(TreeNode cur) {
if(cur == null) return;
traversal(cur.left);// 左
if(pre != null && (cur.val - pre.val) < result){// 中
result = cur.val - pre.val;
}
pre = cur;// 记录前一个
traversal(cur.right);// 右
}
}
501.二叉搜索树中的众数和 530差不多双指针思路,不过 这里涉及到一个很巧妙的代码技巧。
可以先自己做做看,然后看我的视频讲解。
视频讲解:不仅双指针,还有代码技巧可以惊艳到你! | LeetCode:501.二叉搜索树中的众数_哔哩哔哩_bilibili
递归法
#如果不是二叉搜索树(暴力法)
如果不是二叉搜索树,最直观的方法一定是把这个树都遍历了,用map统计频率,把频率排个序,最后取前面高频的元素的集合。
- 这个树都遍历了,用map统计频率
- 把统计的出来的出现频率(即map中的value)排个序
- 取前面高频的元素
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
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);
}
}
是二叉搜索树
既然是搜索树,它中序遍历就是有序的。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
TreeNode pre = null;
int count = 0;
int maxCount = 0;
ArrayList<Integer> resList;
public int[] findMode(TreeNode root) {
resList = new ArrayList<>();
searchBST(root);
int[] res = new int[resList.size()];
for (int i = 0; i < resList.size(); i++) {
res[i] = resList.get(i);
}
return res;
}
public void searchBST(TreeNode cur) {
if (cur == null) return;
searchBST(cur.left);// 左
// 中
if(pre == null){// 第一个节点
count = 1;// 频率为1
}else if(pre.val == cur.val){// 与前一个节点数值相同
count++;
}else{// 与前一个节点数值不同
count = 1;
}
pre = cur;// 更新上一个节点
// 根据maxCount动态更新结果数组resList
if(count == maxCount){// 如果和最大值相同,放进result中
resList.add(cur.val);
}else if(count > maxCount){// 如果计数大于最大值
maxCount = count;// 更新最大频率
resList.clear();// 很关键的一步,不要忘记清空result,之前result里的元素都失效了
resList.add(cur.val);
}
searchBST(cur.right);// 右
}
}
236. 二叉树的最近公共祖先
遇到这个题目首先想的是要是能自底向上查找就好了,这样就可以找到公共祖先了。
二叉树回溯的过程就是从低到上。
后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。涉及到回溯一定要用后序遍历,因为中要根据左右的结果判断
在递归函数有返回值的情况下:如果要搜索一条边,递归函数返回值不为空的时候,立刻返回,如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)。
需要递归函数返回值,来告诉我们是否找到节点q或者p,那么返回值为bool类型就可以了。
但我们还要返回最近公共节点,可以利用上题目中返回值是TreeNode * ,那么如果遇到p或者q,就把q或者p返回,返回值不为空,就说明找到了q或者p。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// 递归结束条件
if(root == null) return root;//遇到空的话,因为树都是空了,所以返回空。
if(root == p || root == q) return root;//说明找到 q p ,则将其返回,包含了情况2
// 后序遍历
TreeNode left = lowestCommonAncestor(root.left, p, q);//左子树有没有出现p或q 左
TreeNode right = lowestCommonAncestor(root.right, p, q);//右子树有没有出现p或q 右
//中
if(left != null && right != null) return root;// 若找到两个节点说明此时root就是最近公共节点。
if(left == null && right != null) return right;// 若找到一个节点
if(left != null && right == null) return left;// 若找到一个节点
if(left == null && right == null) return null;// 若未找到节点 p 或 q
return null;
}
}
这道题目刷过的同学未必真正了解这里面回溯的过程,以及结果是如何一层一层传上去的。
那么我给大家归纳如下三点:
-
求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从底向上的遍历方式。
-
在回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。
-
要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果。
可以说这里每一步,都是有难度的,都需要对二叉树,递归和回溯有一定的理解。
本题没有给出迭代法,因为迭代法不适合模拟回溯的过程。理解递归的解法就够了。