力扣刷题day18|530二叉搜索树的最小绝对差、501二叉搜索树中的众数、236二叉树的最近公共祖先

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的最近公共祖先。

递归三要素:

  1. 确定递归函数返回值以及参数:参数是根节点,和指定节点p、q,要返回最近的公共父节点
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q)
  1. 确定终止条件:如果找到了 节点p或者q,或者遇到空节点,就返回。
if (root == p || root == q || root == null) return root;
  1. 确定单层递归逻辑:值得注意的是本题函数有返回值,是因为回溯的过程需要递归函数的返回值做判断,但本题我们依然要遍历树的所有节点。

注:递归函数有返回值就是要遍历某一条边,但有返回值也要看如何处理返回值!

如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树呢?

搜索一条边的写法:

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的时候,直接返回当前节点,无需继续递归子树。

为什么情况一(不是一方为另一方的祖先的情况)的代码包含了上述情况呢?

如下图

image-20221011194246873

// 当左右有一个为空
if (left == null && right != null) {
    return right;

根据代码此时遇到节点7直接返回给10

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值