文章目录
- 题目
- 标题和出处
- 难度
- 题目描述
- 要求
- 示例
- 数据范围
- 进阶
- 解法一
- 思路和算法
- 代码
- 复杂度分析
- 解法二
- 思路和算法
- 代码
- 复杂度分析
- 解法三
- 思路和算法
- 代码
- 复杂度分析
题目
标题和出处
标题:二叉搜索树中的众数
出处:501. 二叉搜索树中的众数
难度
3 级
题目描述
要求
给定一个含重复值的二叉搜索树的根结点 root \texttt{root} root,返回二叉搜索树中的所有众数(即出现频率最高的元素)。
如果树中有不止一个众数,可以按任意顺序返回。
假定二叉搜索树满足如下定义:
- 结点左子树中所含结点的值小于等于当前结点的值。
- 结点右子树中所含结点的值大于等于当前结点的值。
- 左子树和右子树都是二叉搜索树。
示例
示例 1:
输入:
root
=
[1,null,2,2]
\texttt{root = [1,null,2,2]}
root = [1,null,2,2]
输出:
[2]
\texttt{[2]}
[2]
示例 2:
输入:
root
=
[0]
\texttt{root = [0]}
root = [0]
输出:
[0]
\texttt{[0]}
[0]
数据范围
- 树中结点数目在范围 [1, 10 4 ] \texttt{[1, 10}^\texttt{4}\texttt{]} [1, 104] 内
- -10 5 ≤ Node.val ≤ 10 5 \texttt{-10}^\texttt{5} \le \texttt{Node.val} \le \texttt{10}^\texttt{5} -105≤Node.val≤105
进阶
你可以使用常数额外空间吗?
解法一
思路和算法
由于二叉搜索树的中序遍历序列是单调递增的,因此二叉搜素树的中序遍历序列中的相同结点值一定相邻。只要得到二叉搜索树的中序遍历序列,即可得到每个结点值的出现次数,并得到众数。
使用递归实现中序遍历的做法是依次访问左子树、根结点和右子树,对于左子树和右子树使用同样的方法访问。
由于中序遍历序列中的相同结点值一定相邻,因此不需要存储完整的中序遍历序列,而是只需要存储上一个遍历到的结点值和出现次数。每次访问结点时,判断当前结点值和上一个结点值是否相等,更新当前结点值的出现次数,然后比较当前结点值的出现次数与最大出现次数,维护二叉搜索树中的众数。
-
如果当前结点值的出现次数等于最大出现次数,则将当前结点值添加到众数列表中。
-
如果当前结点值的出现次数大于最大出现次数,则将最大出现次数更新为当前结点值的出现次数,将众数列表清空后将当前结点值添加到众数列表中。
遍历结束之后,众数列表中的结点值即为二叉搜索树中的全部众数。
对于中序遍历序列中的任意两个相邻结点值,或者结点值不同,或者结点值相同且出现次数不同,因此同一个结点值最多在众数列表中出现一次,不会重复出现。
代码
class Solution {
int prev;
int freq;
int maxFreq;
List<Integer> modesList;
public int[] findMode(TreeNode root) {
prev = Integer.MIN_VALUE;
freq = 0;
maxFreq = 0;
modesList = new ArrayList<Integer>();
inorder(root);
int size = modesList.size();
int[] modes = new int[size];
for (int i = 0; i < size; i++) {
modes[i] = modesList.get(i);
}
return modes;
}
public void inorder(TreeNode node) {
if (node == null) {
return;
}
inorder(node.left);
if (node.val == prev) {
freq++;
} else {
prev = node.val;
freq = 1;
}
if (freq == maxFreq) {
modesList.add(node.val);
} else if (freq > maxFreq) {
maxFreq = freq;
modesList.clear();
modesList.add(node.val);
}
inorder(node.right);
}
}
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉搜索树的结点数。每个结点都被访问一次。
-
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉搜索树的结点数。空间复杂度主要是递归调用的栈空间,取决于二叉搜索树的高度,最坏情况下二叉搜索树的高度是 O ( n ) O(n) O(n)。
解法二
思路和算法
使用迭代实现二叉搜索树的中序遍历的做法是使用栈存储结点。
每次访问结点时,需要判断当前结点值和上一个结点值是否相等,根据结点值和出现次数维护二叉搜索树中的众数。
代码
class Solution {
public int[] findMode(TreeNode root) {
int prev = Integer.MIN_VALUE;
int freq = 0;
int maxFreq = 0;
List<Integer> modesList = new ArrayList<Integer>();
Deque<TreeNode> stack = new ArrayDeque<TreeNode>();
TreeNode node = root;
while (!stack.isEmpty() || node != null) {
while (node != null) {
stack.push(node);
node = node.left;
}
node = stack.pop();
if (node.val == prev) {
freq++;
} else {
prev = node.val;
freq = 1;
}
if (freq == maxFreq) {
modesList.add(node.val);
} else if (freq > maxFreq) {
maxFreq = freq;
modesList.clear();
modesList.add(node.val);
}
node = node.right;
}
int size = modesList.size();
int[] modes = new int[size];
for (int i = 0; i < size; i++) {
modes[i] = modesList.get(i);
}
return modes;
}
}
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉搜索树的结点数。每个结点最多被访问一次。
-
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉搜索树的结点数。空间复杂度主要是栈空间,取决于二叉搜索树的高度,最坏情况下二叉搜索树的高度是 O ( n ) O(n) O(n)。
解法三
思路和算法
解法一和解法二都需要使用栈空间。为了将空间复杂度降低到常数,需要使用莫里斯遍历。
使用莫里斯遍历对二叉搜索树中序遍历时,同样在每次访问结点时判断当前结点值和上一个结点值是否相等,根据结点值和出现次数维护二叉搜索树中的众数。
由于事先无法知道众数的个数,因此需要用众数列表存储全部的众数,最后将众数列表转换成数组。Java 中的数组和列表是两种不同的类型,因此无法做到严格的常数空间复杂度。如果将众数列表看成返回值,则空间复杂度可以视为常数。
代码
class Solution {
public int[] findMode(TreeNode root) {
int prev = Integer.MIN_VALUE;
int freq = 0;
int maxFreq = 0;
List<Integer> modesList = new ArrayList<Integer>();
TreeNode node = root;
while (node != null) {
int curr = Integer.MIN_VALUE;
if (node.left == null) {
curr = node.val;
node = node.right;
} else {
TreeNode predecessor = node.left;
while (predecessor.right != null && predecessor.right != node) {
predecessor = predecessor.right;
}
if (predecessor.right == null) {
predecessor.right = node;
node = node.left;
} else {
predecessor.right = null;
curr = node.val;
node = node.right;
}
}
if (curr != Integer.MIN_VALUE) {
if (curr == prev) {
freq++;
} else {
prev = curr;
freq = 1;
}
if (freq == maxFreq) {
modesList.add(curr);
} else if (freq > maxFreq) {
maxFreq = freq;
modesList.clear();
modesList.add(curr);
}
}
}
int size = modesList.size();
int[] modes = new int[size];
for (int i = 0; i < size; i++) {
modes[i] = modesList.get(i);
}
return modes;
}
}
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉搜索树的结点数。使用莫里斯遍历,每个结点最多被访问两次。
-
空间复杂度: O ( 1 ) O(1) O(1)。不考虑返回值以及与返回值相关的临时空间时,空间复杂度是 O ( 1 ) O(1) O(1)。