题目
题目来源:力扣(LeetCode)
给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。
假定 BST 有如下定义:
结点左子树中所含结点的值小于等于当前结点的值
结点右子树中所含结点的值大于等于当前结点的值
左子树和右子树都是二叉搜索树
例如:
给定 BST [1,null,2,2],
1
\
2
/
2
返回[2].
思路分析
中序遍历
二叉搜索树的中序遍历结果是一个升序序列,那么在序列中重复出现的数字一定是连续出现的。
1<br /> / \<br /> 0 2<br /> / \ /<br />-1 0 2
例如上面的二叉搜索的中序遍历序列是 [-1, 0, 0, 1, 2, 2] ,序列中的 0 和 2 是重复出现的,并且所有的 0 都集中在一个连续的段内,所有的 2 也集中在一个连续的段内。
因此我们可以在中序遍历的过程中,对遍历到的当前元素进行处理。使用 base 记录当前遍历的数字,用 count 记录当前数字重复的次数,用 maxCount 来维护已经扫描过的数当中出现最多的那个数字的出现次数,用 answer 数组记录出现的众数。每次遍历到一个新的元素时:
- 首先更新 base 和 count:
- 如果该元素和 base 相等,那么 count 自增 1;
- 否则将 base 更新为当前数字,count 复位为 1。
- 然后更新 maxCount:
- 如果 count=maxCount,那么说明当前的这个数字(base)出现的次数等于当前众数出现的次数,将 base 加入 answer 数组;
- 如果 count>maxCount,那么说明当前的这个数字(base)出现的次数大于当前众数出现的次数,因此,我们需要将 maxCount 更新为 count,清空 answer 数组后将base 加入answer 数组。
代码
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var findMode = function (root) {
// 记录当前遍历的数字
let base = 0;
// 记录当前遍历数字重复的次数
let count = 0;
// 维护已经遍历过的数当中出现最多的那个数字的出现次数
let maxCount = 0;
// 记录出现的众数
let answer = [];
// 每次遍历到一个数时更新维护上面的四个变量
const update = (x) => {
// 如果当前遍历的数字与已经遍历过的某个数字相同,则 count 自增 1
if (x === base) {
++count;
} else {
// 如果当前遍历的数字是一个新的数字,则将 base 更新为当期的数字,count 重置为 1
count = 1;
base = x;
}
// 如果当前数字重复的次数与出现最多的那个数字的出现次数相等,将 base 加入 answer 数组
if (count === maxCount) {
answer.push(base);
}
// 如果当前数字重复的次数大于出现最多的那个数字的出现次数,即 count > maxCount
// 将 maxCount 重置为 count,并且清空answer 数组后将 base 加入 answer 数组
if (count > maxCount) {
maxCount = count;
answer = [base];
}
}
// 递归中序遍历二叉搜索树
const dfs = (node) => {
if (!node) {
return;
}
// 遍历左子树
dfs(node.left);
// 遍历根节点(在这里对当前节点进行处理)
update(node.val);
// 遍历右子树
dfs(node.right);
}
dfs(root);
return answer;
};
Morris 中序遍历
Morris 中序遍历的一个重要步骤就是寻找当前节点的前驱节点,并且 Morris 中序遍历寻找下一个点始终是通过转移到 right 指针指向的位置来完成的。
morris遍历的实现原则
记当前节点为cur:
- 如果cur无左孩子,cur向右移动(cur=cur.right)
- 如果cur有左孩子,找到cur左子树上最右的节点,记为mostright
- 如果mostright的right指针指向空,让其指向cur,cur向左移动(cur=cur.left)
- 如果mostright的right指针指向cur,让其指向空,cur向右移动(cur=cur.right)
实现以上的原则,即实现了morris遍历。
var findMode = function(root) {
let base = 0, count = 0, maxCount = 0;
let answer = [];
const update = (x) => {
if (x === base) {
++count;
} else {
count = 1;
base = x;
}
if (count === maxCount) {
answer.push(base);
}
if (count > maxCount) {
maxCount = count;
answer = [base];
}
}
let 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;
}
}
return answer;
};
morris 遍历参考:
https://zhuanlan.zhihu.com/p/101321696 https://blog.csdn.net/yangfeisc/article/details/45673947 https://www.zhihu.com/zvideo/1399844310284697600 https://www.jianshu.com/p/959247c29a7b