题目: 给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。
示例:
思路分析:
一、寻找众数,即统计出现次数,一般思路会想到使用Hash Map来计数,但仔细研读题目,得知是在一棵BST中寻找众数,即 BST的中序遍历会得到一个有序数组 ,在一个有序数组中寻找众数,使用双指针即可解决。
二、利用BST中序遍历得到有序数组的特性,使用双指针的做法,需要 TreeNode pre 来记录上一个节点,使用 curTimes 记录当前值出现的次数,使用 maxTimes 记录某个可能的众数出现的次数(即最大出现次数)。在整个流程中使用 List 进行众数的存放以及更新。
三、在中序遍历流程中
1.如果pre == null,说明其为遍历的第一个节点,不需要特别处理(第一个结点的初条件在主函数中已经给出,即令curTimes = 1)。
2.在辅助函数midTravelser(以下简称mid)中,x.left即进到最左结点开始进行遍历,然后回到x,再去到x.right,即中序遍历的正确顺序。
3.如果当前结点与上一结点值相同,则进行curTimes++。
4.否则,将进行判断上一结点值的curTimes与maxTimes的比较,如果cur > max,则令max=cur,并且清空存放可能众数的items,再将上一结点添加进去;如果cur=max,则将上一结点值一并添加进items;除此之外,上一结点的数值一定不是众数,则继续保存items当前值即可;最后,将curTimes重制为1,表示当前数字已经出现一次。
5.更新pre,即令pre = x。
6.根据中序遍历的顺序,此时需要进入右宝宝。
四、主函数流程
1.处理特殊情况,如果root == null,直接返回int[0]。
2.因为初始结点情况特殊,即初始化,最左边结点在中序遍历的递归中不处理,故需要先将curTimes与maxTimes都赋值为1,此剧代表最左边的结点我们已经在主函数中进行处理。
3.调用辅助函数后,还需要处理最后一个结点的值,因为在递归过程中不会再有下一个结点值同其相等或不相等来进行判断其是否为众数,故需要在主函数中作出处理:(1)如果cur > max,即说明最后一个结点值才是唯一众数,则更新众数为最后一个结点值。(2)如果cur=max则对items进行添加即可。
4.将List转换为数组进行返回即可。
算法复杂度分析
1.时间复杂度O(N):因为中序遍历需要对整棵树所有节点进行逐一排查。
2.空间复杂度:如果不考虑递归产生隐形调用栈的情况的话,可将认为空间复杂度为常数,且中间状态List中的元素并不会很多。
Code-递归
class Solution {
List<Integer> items;
int cur;
int max;
TreeNode pre;
public int[] findMode(TreeNode root) {
if (root == null) {
return new int[0];
}
cur = 1;
max = 1;
midTravelser(root);
if (cur > max) {
return new int[]{pre.val};
} else if (cur == max) {
items.add(pre.val);
}
int[] res = new int[items.size()];
for (int i = 0; i < res.length; i++) {
res[i] =items.get(i);
}
return res;
}
public void midTravelser(TreeNode x) {
if (x == null) {
return;
}
midTravelser(x.left);
if (pre != null) {
if (x.val != pre.val) {
if (cur > max) {
max = cur;
items.clear();
items.add(pre.val);
} else if (cur == max) {
items.add(pre.val);
}
cur = 1;
} else {
cur++;
}
}
pre = x;
midTravelser(x.right);
}
}
PS:本题涉及细节
1.BST中序遍历的特性。
2.双指针算法(后续细节在此更新)。
3.辅助函数中需要注意的细节以及if函数后的各个细节。