530. 二叉搜索树的最小绝对差
给你一个二叉搜索树的根节点 root
,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
示例 1:
输入:root = [4,2,6,1,3]
输出:1
示例 2:
输入:root = [1,0,48,null,null,12,49]
输出:1
递归思路
遇到在二叉搜索树上求什么最值,求差值之类的,都要思考一下二叉搜索树可是有序的,要利用好这一特点。
- 双指针法
cur指针:指向现在的节点
pre指针:指向前一个节点
当cur为叶子节点时,pre为null,不会参与判断,直到返回上一层pre再指向叶子节点,可以计算差值,用一个全局变量存储最小差
完整代码
// 递归--------------------------------------------------
int minVal = Integer.MAX_VALUE;
TreeNode pre = null;
public int getMinimumDifference(TreeNode root) {
traversal(root);
return minVal;
}
void traversal (TreeNode cur) {
if (cur == null) return;
// 左
traversal(cur.left);
// 中
if (pre != null) {
minVal = Math.min(minVal, cur.val - pre.val);
}
pre = cur; // 记录前一个
// 右
traversal(cur.right);
}
// 递归--------------------------------------------------
中序迭代思路
用栈存储节点
// 迭代--------------------------------------------------
public int getMinimumDifference1(TreeNode root) {
if (root == null) return 0;
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
TreeNode pre = null;
int res = Integer.MAX_VALUE;
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. 二叉搜索树中的众数
给你一个含重复值的二叉搜索树(BST)的根节点 root
,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。
假定 BST 满足如下定义:
- 结点左子树中所含节点的值 小于等于 当前节点的值
- 结点右子树中所含节点的值 大于等于 当前节点的值
- 左子树和右子树都是二叉搜索树
示例 1:
输入:root = [1,null,2,2]
输出:[2]
示例 2:
输入:root = [0]
输出:[0]
普通递归
暴力解:全部元素和频率存进map,对频率排序,取出最高频率存入数组
// 暴力解------------------------------------------------
public int[] findMode(TreeNode root) {
// key用来存数,value用来存频率
HashMap<Integer, Integer> map = new HashMap<>();
// 记录众数
List<Integer> res = new ArrayList<>();
if (root == null) {
return res.stream().mapToInt(Integer::intValue).toArray(); // 将 List 元素存储到数组中
}
// 获得频率map
searchBST(root, map);
// 1:把map转换成entryset,再转换成保存Entry对象的list。
List<Map.Entry<Integer, Integer>> mapList = new ArrayList<>(map.entrySet());
// 2:调用Collections.sort(list,comparator)方法把Entry-list排序
Collections.sort(mapList, (c1, c2) -> c2.getValue().compareTo(c1.getValue()));
// 把降序的第一个元素存进来
res.add(mapList.get(0).getKey());
// 把频率最高的加入 list
for (int i = 1; i < mapList.size(); i++) {
if (mapList.get(i).getValue() == mapList.get(i - 1).getValue()) {
res.add(mapList.get(i).getKey());
} else {
break;
}
}
return res.stream().mapToInt(Integer::intValue).toArray();
}
void searchBST(TreeNode root, HashMap<Integer, Integer> map) {
if (root == null) {
return;
}
map.put(root.val, map.getOrDefault(root.val, 0) + 1);
searchBST(root.left, map);
searchBST(root.right,map);
}
// 暴力解------------------------------------------------
BST递归
根据二叉搜索树的性质,是搜索树的话中序遍历就是有序的,可以比较其树的相邻节点,不相等则频率记0,相等则频率加1
难点
-
双指针:
弄一个指针指向前一个节点,这样每次cur(当前节点)才能和pre(前一个节点)作比较。而且初始化的时候pre = NULL,这样当pre为NULL时候,我们就知道这是比较的第一个元素。
-
如何只遍历一次就找到所有的众数?
如果频率count 等于 maxCount(最大频率),把这个元素加入到res
但是当maxCount此时不是真正最大频率,频率count 大于 maxCount的时候,不仅要更新maxCount,而且要清空res
-
list转为int数组
list.stream().mapToInt(Integer::intValue).toArray();
完整代码:
// 根据BST性质--------------------------------------------
// 定义几个全局变量
List<Integer> res = new ArrayList<>();
int maxCount; // 最大频率
int count; // 统计频率
TreeNode pre = null;
public int[] findMode1(TreeNode root) {
traversal(root);
return res.stream().mapToInt(Integer::intValue).toArray();
}
void traversal(TreeNode cur) {
if (cur == null) {
return;
}
// 左
traversal(cur.left);
// 中
// 当pre是空时代表是第一个数
if (pre == null) {
count = 1;
}else if (pre.val == cur.val) { // 与前一个节点数值相同
count++;
}else { // 与前一个节点数值不同,表示cur这个数第一次出现
count = 1;
}
pre = cur; //更新
// 如果和最大值相同,放进result中
if (count == maxCount) {
res.add(cur.val);
}else if (count > maxCount) { // 如果计数大于最大值频率
res.clear();
res.add(cur.val);
maxCount = count; // 更新最大频率
}
// 右
traversal(cur.right);
return;
}
// 根据BST性质--------------------------------------------
236. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
示例 2:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。
示例 3:
输入:root = [1,2], p = 1, q = 2
输出:1
思路
从p、q节点自底向上查找,就要用到回溯,后序遍历就是天然的回溯过程,最先处理的一定是叶子节点。
如何判断一个节点是节点q和节点p的公共公共祖先呢?
如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。
递归三要素:
- 确定递归函数返回值以及参数:参数是根节点,和指定节点p、q,要返回最近的公共父节点
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q)
- 确定终止条件:如果找到了 节点p或者q,或者遇到空节点,就返回。
if (root == p || root == q || root == null) return root;
- 确定单层递归逻辑:值得注意的是本题函数有返回值,是因为回溯的过程需要递归函数的返回值做判断,但本题我们依然要遍历树的所有节点。
注:递归函数有返回值就是要遍历某一条边,但有返回值也要看如何处理返回值!
如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树呢?
搜索一条边的写法:
if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;
搜索整个树写法:
left = 递归函数(root->left);
right = 递归函数(root->right);
left与right的逻辑处理;
在递归函数有返回值的情况下:如果要搜索一条边,递归函数返回值不为空的时候,立刻返回,如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)。
难点
容易忽略一个情况:就是节点本身p(q),它拥有一个子孙节点q§。
只需要找到一个节点是p或者q的时候,直接返回当前节点,无需继续递归子树。
为什么情况一(不是一方为另一方的祖先的情况)的代码包含了上述情况呢?
如下图
// 当左右有一个为空
if (left == null && right != null) {
return right;
根据代码此时遇到节点7直接返回给10