题目
题目来源:力扣(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 自增 1if (x === base) {++count;} else {// 如果当前遍历的数字是一个新的数字,则将 base 更新为当期的数字,count 重置为 1count = 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
