刷刷刷 Day 21 | 501. 二叉搜索树中的众数
501. 二叉搜索树中的众数
LeetCode题目要求
给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。
假定 BST 满足如下定义:
结点左子树中所含节点的值 小于等于 当前节点的值
结点右子树中所含节点的值 大于等于 当前节点的值
左子树和右子树都是二叉搜索树
示例
输入:root = [1,null,2,2]
输出:[2]
解题思路
在二叉搜索树中,我们要找到出现频率最高的元素,那么根据二叉搜索树的特性,我们首先可以想到的是像数组一样,统计每个元素出现的个数。
那么最简单的方法是遍历二叉搜索树,并通过 Map 记录元素及元素出现的次数;然后最元素出现的次数降序排序;最后遍历 Map.values,由于它已经是降序排序过的,那么通过第一个元素出现的次数与后面的对比,就把所有最高频率出现的元素找到了。 以下就是代码实现:
// 暴力方式
public int[] findMode1(TreeNode root) {
// 出现频率最高的元素, 数量最大的元素
// 存在相同的数,找到最大相同的元素,如果存在多个也要输出
// 不存在相同数,那么就是把所有的元素直接删除,因为它们的数量都是1,也是最高频率
if (root.left == null && root.right == null) {
return new int[] {root.val};
}
List<Integer> res = new ArrayList<>();
// 怎么知道数量不相等,怎么知道数量都相等呢,用 map 存储数值及出现的频率,频率排序
Map<Integer,Integer> map = new HashMap<>();
traversal1(root, map);
// 对 map 值排序
// List<Map.Entry<Integer, Integer>> entryList = map.entrySet().stream()
// .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
// .collect(Collectors.toList());
List<Map.Entry<Integer, Integer>> entryList = map.entrySet().stream()
.sorted((c1, c2) -> c2.getValue().compareTo(c1.getValue()))
.collect(Collectors.toList());
res.add(entryList.get(0).getKey());
for (int i = 1; i < entryList.size(); i++) {
if (entryList.get(i).getValue() == entryList.get(i-1).getValue()) {
res.add(entryList.get(i).getKey());
} else {
break;
}
}
int[] result = new int[res.size()];
for (int i = 0; i < res.size(); i++) {
result[i] = res.get(i);
}
return result;
// return res.stream().mapToInt(Integer::intValue).toArray();
}
private void traversal1(TreeNode node, Map<Integer,Integer> map) {
// 中序遍历, 左中右
if (node == null) {
return;
}
traversal1(node.left, map);
map.put(node.val, map.getOrDefault(node.val, 0) + 1);
traversal1(node.right, map);
}
然而上面的方法我们在遍历二叉树的时候,用了额外的Map、元素排序等操作,使得处理效率比较低。
其实可以通过类似双指针的方式,使用 pre 节点,count,maxCount 几个变量对于元素的记录,完成众数的记录。具体参见代码及详细注释
递归迭代,上代码
class Solution {
// 记录每个元素出现的次数
private int count;
// 记录每个元素出现的最大次数
private int maxCount;
// 临时存储前一个节点
private TreeNode pre;
// 结果集
private List<Integer> res;
public int[] findMode(TreeNode root) {
// 初始化 List
res = new ArrayList<>();
// 调用 递归方法,获取众数
traversal(root);
// 遍历结果 List,转换成 数组
int[] result = new int[res.size()];
for (int i = 0; i < res.size(); i++) {
result[i] = res.get(i);
}
return result;
}
// 递归,一次取结果
private void traversal(TreeNode node) {
// 递归终止条件,当节点为空时就返回
if (node == null) {
return;
}
// 采用中序遍历 左 --> 中 --> 右 , 输出的是个有序结果
// 左,递归左节点
traversal(node.left);
// 中 节点处理,这里会对元素及值进行处理
// 如果是第一次递归,那么 pre 为 null ,此时 node count 计数为1;同时如果 pre 不为 null 时且与 node 的值不相等,那也要 count 计数为 1
if (pre == null || pre.val != node.val) {
count = 1;
} else if (pre.val == node.val) {
// 因为是有序结果,那么前一节点 pre 的值可能和当前 node 节点的值相等,那么就将 count 数量 + 1
count++;
}
// 移动 pre,遍历过程中,相当于指针的移动, pre 要向后移动了
pre = node;
// 经过上面的操作后,count 可能已经出现众数结果了,如果已经达到 maxCount 就要输出结果,
// 但这里的疑惑点在于 maxCount 在开始还是初始值,但是没问题,因为众数频率可能都是 1 呢。后面还会对他们进行处理
if (count == maxCount) {
res.add(node.val);
}
// 上一步的处理结果虽然将结果放入结果集,但是经过递归后 count 有了变化,可能会大于之前的 maxCount,
// 当出现了新的最大值,就要清空结果集,重新将众数放入结果集
if (count > maxCount) {
maxCount = count;
res.clear();
res.add(node.val);
}
// 右节点递归
traversal(node.right);
}
}
重难点
重点还是要掌握二叉搜索树的特性,及使用中序遍历能输出的是个有序结果。同时这个题目也比较特殊的是有相等的元素。
附:学习资料链接