代码随想录算法训练营day21 | 530.二叉搜索树的最小绝对差 501.二叉搜索树中的众数 236. 二叉树的最近公共祖先

136 阅读6分钟

530.二叉搜索树的最小绝对差

给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。

530. 二叉搜索树的最小绝对差 - 力扣(Leetcode)

思路

给定一组数,任意两个数差值最小的情况出现在这组数中任意相邻的两个数中。
二叉搜索树按照中序遍历的结果是自然排序的。
在遍历过程中,记录相邻的两个节点precur,当前节点为cur,其上一个遍历的节点为pre
记录当前最小绝对差为minDifference,当cur.val - pre.val小于minDifference时,更新minDifference

代码

/**
 * 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 minDifference = Integer.MAX_VALUE;

    public int getMinimumDifference(TreeNode root) {
        traversal(root);
        return minDifference;
    }

    private void traversal(TreeNode cur){

        if(cur == null){
            return;
        }

        // 左
        traversal(cur.left);

        // 中
        if(pre != null){
            // 此处不用Math.abs()的原因:
            // 二叉搜索树中 pre.val < cur.val
            minDifference = Math.min(minDifference, cur.val - pre.val);       
        }

        // 记录当前节点为下一次比较中的pre
        pre = cur;

        traversal(cur.right);

    }
}

501.二叉搜索树中的众数

给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。 如果树中有不止一个众数,可以按 任意顺序 返回。 假定 BST 满足如下定义:

  • 结点左子树中所含节点的值 小于等于 当前节点的值
  • 结点右子树中所含节点的值 大于等于 当前节点的值
  • 左子树和右子树都是二叉搜索树

思路

  • 遍历二叉树,使用哈希表 map 记录每个节点数值及其出现的频数,同时在遍历过程中,不断更新最大频数maxFrequency
  • 遍历map,找到所有频数为maxFrequency的键,保存到list中。
  • list转成数组,并返回。

代码

/**
 * 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 {
    // 在遍历过程中统计最大的频率
    int maxFrequency = 0;

    public int[] findMode(TreeNode root) {
        Map<Integer,Integer> map = new HashMap<>();

        traversal(root, map);

        // 遍历map
        // 找出值为 maxFrequency 的键
        List<Integer> list = new ArrayList<>();

        for(Map.Entry<Integer, Integer> entry: map.entrySet()){
            if(entry.getValue() == maxFrequency){
                list.add(entry.getKey());
            }
        }

        return list.stream().mapToInt(o -> o).toArray();
    }

    private void traversal(TreeNode node,Map<Integer,Integer> map){

        if(node == null){
            return;
        }

        traversal(node.left, map);

        int frequency = map.getOrDefault(node.val, 0) + 1;
        maxFrequency = Math.max(maxFrequency, frequency);
        map.put(node.val, frequency);

        traversal(node.right, map);
    }
}

236. 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

提示:

  • 树中节点数目在范围 [2, 105] 内。
  • -109 <= Node.val <= 109
  • 所有 Node.val 互不相同 。
  • p != q
  • p 和 q 均存在于给定的二叉树中。

236. 二叉树的最近公共祖先 - 力扣(Leetcode)

思路

思路1
遍历二叉树,在过程中,使用path记录当前路径。
使用hasBothhasPhasQ分别记录找到一条同时含有pq的路径、找到一条含有p的路径和找到一条含有q的路径的情况。
使用paths保存找到的路径。

  • hasBothtrue时,表示已经找到一条包含pq的路径,之后所有的遍历直接跳过。
  • paths最终只包含 1项2项 ,当且仅当找到一条同时含有pq的路径时包含1项。
  • 对于叶子节点,
    • 首先将该节点加入路径中,
    • 如果当前路径中包含了pq两个节点,将paths清空,再将当前路径保存到paths中,并将hasBoth设置为true
    • 否则,如果当前路径中包含了p,且hasPfalse,这意味着paths中尚未保存含有p的路径,将当前路径保存到paths中,并将hasP设置为true
    • 同理,如果当前路径中包含了q,且hasQfalse,这意味着paths中尚未保存含有q的路径,将当前路径保存到paths中,并将hasQ设置为true
  • 当遍历结束,判断paths的大小。
    • 如果paths中只有一个数据项,说明pq在同一条路径上,遍历该路径,如果第一个遍历到p,意味着pq的祖先(/父)节点,将p返回;同理,如果先遍历到q,则返回q
    • 否则,pq不是直接祖孙关系,分别在两条路径中;
      • 先分别遍历两条路径,定位到这两个节点在路径中的位置;
      • 从定位的下标向0下标回溯,找到最近的相同值,该元素就是pq的最近公共祖先的值。
      • 通过该值在树中找到该节点,并返回。

思路2
找祖先是自下而上遍历,即先搜索节点的子树,再搜索节点本身,符合该要求的遍历方式是后序遍历,遍历顺序是左右中

对于二叉树的一个节点,

  • 如果当前节点就是目标节点,或者是null,直接返回当前节点。
  • 如果它的左子树中找到一个目标节点,右子树中找到另一个目标节点,那么当前节点为这两个节点的公共祖先。
  • 由于后序遍历先遍历子树,再遍历当前节点,所以两个节点的最近公共祖先节点较其他公共祖先节点先遍历到,因为最近公共祖先节点是其他公共祖先节点的子孙节点。
  • 如果它的其中的一个子树中返回了节点,另一个子树没有找到目标,这就出现了两种情况
    • 找到目标的子树中含有最近公共祖先,即当前节点的子树中同时含有两个目标节点,子树中返回的目标就是所求的最近公共祖先,因此返回这个结果即可;
    • 找到目标的子树中只含有其中一个目标值,而另一棵子树中也未找到目标值,那么当前节点还不是最近公共祖先,最近公共祖先还在当前节点的祖先节点中,将找到的目标返回即可。

代码

思路1的代码:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    boolean hasBoth = false;
    boolean hasP = false;
    boolean hasQ = false;

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        List<List<Integer>> paths = new ArrayList<>();
        List<Integer> path = new ArrayList<>();

        traversal(root, p, q, paths,path);

        if(paths.size() == 1){
            // 在同一条路径上
            for(int i=0;i<paths.get(0).size();i++){
                if(paths.get(0).get(i) == p.val){
                    return p;
                }

                if(paths.get(0).get(i) == q.val){
                    return q;
                }

            }

            return null;
        }

        // 两条路径
        int index1 = 0,index2 =0;
        for(int i=0;i<paths.get(0).size();i++){
            if(paths.get(0).get(i) == p.val || paths.get(0).get(i) == q.val){
                index1 = i;
                break;
            }
        }

        for(int i=0;i<paths.get(1).size();i++){
            if(paths.get(1).get(i) == p.val || paths.get(1).get(i) == q.val){
                index2 = i;
                break;
            }
        }

        for(int i = index1; i>=0; i--){
            for(int j = index2; j>=0; j--){
                if(paths.get(0).get(i) == paths.get(1).get(j)){
                    return getNode(root, paths.get(0).get(i));
                }
            }
        }

        return null;

    }

    private void traversal(TreeNode node,TreeNode p, TreeNode q, List<List<Integer>> paths,List<Integer> path){

        if(hasBoth){
            return;
        }

        if(node.left == null && node.right == null){
            path.add(node.val);

            if(path.contains(p.val) && path.contains(q.val)){
                hasBoth = true;
                paths.clear();
                paths.add(new ArrayList<Integer>(path));
            }else if(path.contains(p.val) && hasP == false){
                paths.add(new ArrayList<Integer>(path));
                hasP = true;
                
            }else if(path.contains(q.val) && hasQ == false){
                paths.add(new ArrayList<Integer>(path));
                hasQ = true;
            }

            path.remove(path.size() -1);

            return;
        }

        // 中
        path.add(node.val);

        // 左
        if(node.left != null){
            traversal(node.left, p,q,paths,path);
        }

        // 右
        if(node.right != null){
            traversal(node.right,p, q, paths, path);
        }

        // 回退
        path.remove(path.size() - 1);

    }

    private TreeNode getNode(TreeNode node,Integer num){
        if(node == null){
            return null;
        }

        if(node.val == num){
            return node;
        }

        TreeNode left = getNode(node.left,num);

        TreeNode right = getNode(node.right, num);

        return left == null? right : left;
    }
}

思路2的代码

/**
 * 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 == p || root == q || root == null){
            return root;
        }

        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right =lowestCommonAncestor(root.right, p, q);

        // 左右子树分别找到一个节点
        if(left != null && right != null){
            return root;
        }

        // 至多一侧有节点
        return left == null ? right : left;
    }
}