代码随想录算法训练营第二一天| 二叉搜索树、最近公共祖先

LeetCode 530. 二叉搜索树的最小绝对差
LeetCode 501. 二叉搜索树的众数
LeetCode 236. 二叉树的最近公共祖先

二叉搜索树的最小绝对差

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

这个题和前面做的验证二叉搜索树类似。因为BST是有序的,我们把它保存成一个集合验证递增是没问题的,那相应的在树里操作也应该是没问题的。只需要保存前一个节点,将现在的节点和前一个节点作比较即可。
对于最小绝对差,题目中说“任意两不同节点值”,但我觉得应该是相邻节点值。
那么,我们也取保存前一个节点用作比较即可。 BUT how ?

递归方法:中序遍历

/**
 * 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 result = Integer.MAX_VALUE;
 	TreeNode pre = null;
 	public int getMinimumDifference(TreeNode root) {
 		traversal(root);
 		return result;
 	}	
	private void traversal(TreeNode cur) {
		if (cur == null) return;
		traversal(cur.left);     // 左
		if (pre != null) {
			result = Math.min(result, cur.val - pre.val); // 中
		}
		pre = cur;
        traversal(cur.right); // 右
	}
 }

迭代法:

class Solution {
    public int getMinimumDifference(TreeNode root) {
        if (root == null) return 0;
        TreeNode pre = null;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        int result = Integer.MAX_VALUE;
        while (cur != null || !stack.isEmpty()) {
            if (cur != null) {
                stack.push(cur);
                cur = cur.left;   // 左
            }
            else {
                cur = stack.pop();
                if (pre != null) {   // 中
                    result = Math.min(result, cur.val - pre.val);
                }
                pre = cur;
                cur = cur.right;   // 右
            }
        }
        return result;
    }
}

二叉搜索树的众数

二叉搜索树中序遍历是有序的。
在这里插入图片描述

遍历有序数组的元素出现频率,从头遍历,那么一定是相邻两个元素作比较,然后就把出现频率最高的元素输出就可以了。

使用pre指针和cur指针的技巧,弄一个指针指向前一个节点,这样每次cur(当前节点)才能和pre(前一个节点)作比较。

初始化的时候pre = NULL,这样当pre为NULL时候,我们就知道这是比较的第一个元素。

if (pre == null || rootValue != pre.val) {  // 第一个节点 / 与前一个节点数值相同
    count = 1;
} else {   // 与前一个节点数值不同
    count++;
} 
pre = cur; // 更新上一个节点

要求最大频率的元素集合(注意是集合,不是一个元素,可以有多个众数)

应该是先遍历一遍数组,找出最大频率(maxCount),然后再重新遍历一遍数组把出现频率为maxCount的元素放进集合。(因为众数有多个) 这种方式遍历了两遍数组。

那么如何只遍历一遍呢?

频率count 大于 maxCount的时候,不仅要更新maxCount,而且要清空结果集(以下代码为result数组),因为结果集之前的元素都失效了。

if (count > maxCount) { // 如果计数大于最大值
    resList.clear();    // 很关键的一步,不要忘记清空result,之前result里的元素都失效了
    resList.add(rootValue);
    maxCount = count;   // 更新最大频率
}
/**
 * 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 {
    ArrayList<Integer> resList;
    int maxCount;
    int count;
    TreeNode pre;
    public int[] findMode(TreeNode root) {
        resList = new ArrayList<>();
        maxCount = 0;
        count = 0;
        pre = null;
        findMode1(root);
        return resList.stream().mapToInt(Integer::intValue).toArray();
        // int[] res = new int[resList.size()];
        // for (int i = 0; i < resList.size(); i++) {
        //     res[i] = resList.get(i);
        // }
        // return res;
    }
    public void findMode1(TreeNode root) {
        if (root == null) {
            return;
        }
        findMode1(root.left);

        int rootValue = root.val;
        // 计数
        if (pre == null || rootValue != pre.val) {
            count = 1;
        } else {
            count++;
        } 

        // 更新结果以及maxCount
        if (count > maxCount) {
            resList.clear();
            resList.add(rootValue);
            maxCount = count;
        } else if (count == maxCount) {
            resList.add(rootValue);
        }
        pre = root;

        findMode1(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();
    }
}

插播

.mapToInt() 方法 是 Java 8 中 流 API 的一种转换方法,它可以将一个流中的元素转换为 int 类型。

该方法接收一个 ToIntFunction 类型的参数 mapper,它是一个函数式接口,定义了一个 applyAsInt() 方法,可以将流中的元素转换为 int 类型。

list.stream().mapToInt(Integer::intValue).toArray();

二叉树的最近公共祖先

自底向上查找 -> 回溯 -> 后序遍历

在递归函数有返回值的情况下:如果要搜索一条边,递归函数返回值不为空的时候,立刻返回,如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)。

搜索一条边的写法:

if (递归函数(root->left)) return ;

if (递归函数(root->right)) return ;

搜索整个树写法:

left = 递归函数(root->left);  // 左
right = 递归函数(root->right); // 右
left与right的逻辑处理;         // 中 

如果left 和 right都不为空,说明此时root就是最近公共节点。这个比较好理解

如果left为空,right不为空,就返回right,说明目标节点是通过right返回的,反之依然。

如果left和right都为空,则返回left或者right都是可以的,也就是返回空。

在这里插入图片描述

/**
 1. Definition for a binary tree node.
 2. public class TreeNode {
 3.     int val;
 4.     TreeNode left;
 5.     TreeNode right;
 6.     TreeNode(int x) { val = x; }
 7. }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == q || root == p || root == null) return root; // 递归结束条件

        // 后序遍历
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);

        if (left == null && right == null) { // 若未找到节点 p 或 q
            return null;
        } else if(left == null && right != null) { // 若找到一个节点
            return right;
        } else if(left != null && right == null) { // 若找到一个节点
            return left;
        } else { // 若找到两个节点
            return root;
        }
    }
}
  1. 求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从底向上的遍历方式。

  2. 在回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。

  3. 要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值