前言
这道leetcode上标签为简单的题目,差点把我整哭了。
一、题目描述
给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。
假定 BST 有如下定义:
结点左子树中所含结点的值小于等于当前结点的值
结点右子树中所含结点的值大于等于当前结点的值
左子树和右子树都是二叉搜索树
例如:
给定 BST [1,null,2,2],
1
\
2
/
2
返回[2].
提示:如果众数超过1个,不需考虑输出顺序
进阶:你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内)
二、最初思路
1.思路
这是一个二叉搜索树,直接递归进行中序遍历,相同的数字将会连续输出,然后在外层设置变量,维护最大值即可。
2.代码
/**
* tempNum:上一个数字
*/
int tempNum;
/**
* 目前数字的个数
*/
int tempCount;
/**
* 数字出现最多个数
*/
int maxCount;
List<Integer> list;
public int[] findMode(TreeNode root) {
if (root == null) {
return new int[0];
}
list = new ArrayList<Integer>();
tempCount = 0;
maxCount = 0;
dfs(root);
int [] res = new int [list.size()];
for (int i = 0; i < res.length; i++) {
res[i] = list.get(i);
}
return res;
}
public void dfs(TreeNode node) {
if (node == null) {
return;
}
dfs(node.left);
/**
* 处理第一个数字
*/
if (maxCount == 0) {
list.add(node.val);
maxCount = tempCount = 1;
tempNum = node.val;
/**
* 非第一个数字
*/
}else {
//当前数字和前一个数字是否相同
if (node.val == tempNum) {
//相同则tempCount++
tempCount ++;
//若maxCount == tempCount,那么此时的val也是众数
if (maxCount == tempCount) {
list.add(node.val);
tempNum = node.val;
}
//如果tempCount > maxCount,那么新的众数出现,清除list
if (maxCount < tempCount) {
maxCount = tempCount;
list.clear();
list.add(node.val);
}
}else {
//当前数字和前一个数字不相同
if (maxCount == 1) {
//如果不存在出现2次的数字,那么任一数字都是众数
list.add(node.val);
tempNum = node.val;
}else {
//与上一个数字相同,重新计数
tempNum = node.val;
tempCount = 1;
}
}
}
dfs(node.right);
}
3、注意点:
- 以下这个两个if千万不要放反了。反了之后,当maxCount < tempCount 之后,maxCount会被赋值为tempCount,那么maxCount == tempCount,则也会执行另一个if,最终导致结果出错。
//若maxCount == tempCount,那么此时的val也是众数
if (maxCount == tempCount) {
list.add(node.val);
tempNum = node.val;
}
//如果tempCount > maxCount,那么新的众数出现,清除list
if (maxCount < tempCount) {
maxCount = tempCount;
list.clear();
list.add(node.val);
}
- “如果不存在出现2次的数字,那么任一数字都是众数。”所以还是不能直接将与上一个数字相同视为重新计数的条件。
//当前数字和前一个数字不相同
if (maxCount == 1) {
//如果不存在出现2次的数字,那么任一数字都是众数
list.add(node.val);
tempNum = node.val;
}else {
//与上一个数字相同,重新计数
tempNum = node.val;
tempCount = 1;
}
}
- 如果特殊情况太多,不要懒~,动手画一下。这道题目思路并不复杂,算法也很简单,但是就是容易绕,所以,做题时还是要认真。
三、优化
递归的过程中,也是要消耗内存的。我们在中序遍历的时候,一定先遍历左子树,然后遍历当前节点,最后遍历右子树。在常规方法中,我们用递归回溯或者是栈来保证遍历完左子树可以再回到当前节点,但这需要我们付出额外的空间代价。我们需要用一种巧妙地方法可以在 O(1)的空间下,遍历完左子树可以再回到当前节点。我们希望当前的节点在遍历完当前点的前驱之后被遍历,我们可以考虑修改它的前驱节点的 right 指针。当前节点的前驱节点的right指针可能本来就指向当前节点(前驱是当前节点的父节点),也可能是当前节点左子树最右下的节点。如果是后者,我们希望遍历完这个前驱节点之后再回到当前节点,可以将它的right 指针指向当前节点。
Morris 中序遍历的一个重要步骤就是寻找当前节点的前驱节点,并且 Morris 中序遍历寻找下一个点始终是通过转移到 \rm rightright 指针指向的位置来完成的。
int base, count, maxCount;
List<Integer> answer = new ArrayList<Integer>();
public int[] findMode(TreeNode root) {
TreeNode cur = root, pre = null;
while (cur != null) {
if (cur.left == null) {
update(cur.val);
cur = cur.right;
continue;
}
pre = cur.left;
while (pre.right != null && pre.right != cur) {
pre = pre.right;
}
if (pre.right == null) {
pre.right = cur;
cur = cur.left;
} else {
pre.right = null;
update(cur.val);
cur = cur.right;
}
}
int[] mode = new int[answer.size()];
for (int i = 0; i < answer.size(); ++i) {
mode[i] = answer.get(i);
}
return mode;
}
public void update(int x) {
if (x == base) {
++count;
} else {
count = 1;
base = x;
}
if (count == maxCount) {
answer.add(base);
}
if (count > maxCount) {
maxCount = count;
answer.clear();
answer.add(base);
}
}
四、tools
在写leetcode的时候,写树的测试用例挺烦的,所以写了一个小工具类,用来将数组转化为树。泛型有点忘记了,这里先放上整型的,后面再改成泛型。
/**
* 这个用来做测试用的,将数组转换为树;这里只实现了整型的,并且null用Integer.MAX_VAL表示
* @author SanfordChern
* @Date 2020-09-25 09:22
* @TODO
*/
public class Array2Tree {
public static TreeNode change(int[] array) {
if (array.length == 0) {
return null;
}
TreeNode root = new TreeNode(array[0]);
dfs(root, array, 0, array.length);
return root;
}
/**
*
* @param node
* @param i node结点的深度(从第0层开始计算)
* @param length 数组的长度
*/
public static void dfs(TreeNode node, int[] array, int i, int length) {
if (2 * i + 1 > length) {
return;
}
if (2 * i + 2 < length) {
if (array[2*i+1] != Integer.MAX_VALUE) {
node.left = new TreeNode(array[2*i+1]);
dfs(node.left, array, 2*i+1, length);
}
if (array[2*i+2] != Integer.MAX_VALUE) {
node.right = new TreeNode(array[2*i+2]);
dfs(node.right, array, 2*i+2, length);
}
}
if(2 * i + 1 == length - 1){
if (array[2*i+1] != Integer.MAX_VALUE) {
node.left = new TreeNode(array[2*i+1]);
dfs(node.left, array, 2*i+1, length);
}
}
}
}
测试:
public static void main(String[] args) {
Test1_2 test1 = new Test1_2();
int m = Integer.MAX_VALUE;
int[] array = new int[] {1,m,2,m,m,2};
TreeNode root = Array2Tree.change(array);
int[] res = test1.findMode(root);
for (int i : res) {
System.out.println(i);
}
}
有那么点小坑,没注意看,leetcode竟然有[1,null,2,2]的树,可能是我看得太少了吧,着实坑,这写法~。自己写tools测的时候,就复制了它的用例,然后就gg了,最后才发现emmm。然后改成[1,m,2,m,m,2]测。