LeetCode 414. 第三大的数
题目描述


题意分析
按不同数值从大到小排名,返回第三名;如果数组中不足三个不同值,就返回最大值。相同数字出现多次只占一个名次,因此这不是把所有元素排序后直接取倒数第三项。
数组非空,值可以为负,也可以等于 32 位整数的最小值。题目进阶要求线性时间;只需要知道前三个不同值,不必给全部元素排序。
解法:维护前三大值
核心思路
[!blue]
扫描时维护当前已见元素中的第一、第二、第三大不同值。新值若等于某个有效名次,直接跳过,避免同一个数占据多个位置;否则从第一名开始比较,找到它能插入的最高名次。
新值成为第一名时,旧第二顺延到第三,旧第一顺延到第二,再设置新第一;成为第二名时,只把旧第二顺延到第三;成为第三名时直接替换第三。更新从后向前进行,防止先覆盖前面的值后丢失需要顺延的旧数据。
每个名次都有独立的存在标记。初始化的零只是存储占位,只有标记为真才参与比较和去重。顺延时要同时移动数值与标记,这样未凑满三名时不会把占位值误当成真实候选,合法的最小整数也无需充当哨兵。
不在前三名的新值可以丢弃:它不影响当前答案,而随着更多数字出现,前三名的门槛只会提高,不会让这个较小值重新变得必要。因此只对当前三个名次去重就够了,无需全局去重集合。
处理结束时,如果第三名有效就返回它,否则返回第一名。输入非空保证第一名一定存在;不同值不足三个时,题目要求的是最大值,而不是已有的最后一名。
解题步骤
- 初始化三个值及各自的存在标记,全部名次初始无效。
- 遍历新值,若与任一有效名次相同就跳过。
- 依次判断能否进入第一、第二或第三名,按从后向前的顺序同步移动旧值和标记。
- 扫描完成,第三名存在就返回第三,否则返回第一。
代码实现
class Solution {
// 只需要维护当前第一大、第二大、第三大三个不同值,不必排序整个数组。
public int thirdMax(int[] nums) {
int first = 0;
int second = 0;
int third = 0;
boolean hasFirst = false;
boolean hasSecond = false;
boolean hasThird = false;
for (int num : nums) {
// 先对有效名次去重,再进行插入排序
boolean seen =
(hasFirst && num == first)
|| (hasSecond && num == second)
|| (hasThird && num == third);
if (seen) {
continue;
}
if (!hasFirst || num > first) {
// 从后往前移动名次,数值和存在标记一起更新
third = second;
hasThird = hasSecond;
second = first;
hasSecond = hasFirst;
first = num;
hasFirst = true;
} else if (!hasSecond || num > second) {
// 从后往前移动名次,数值和存在标记一起更新
third = second;
hasThird = hasSecond;
second = num;
hasSecond = true;
} else if (!hasThird || num > third) {
third = num;
hasThird = true;
}
}
if (hasThird) {
return third;
}
return first;
}
}
func thirdMax(nums []int) int {
// 只需要维护当前第一大、第二大、第三大三个不同值,不必排序整个数组。
first, second, third := 0, 0, 0
hasFirst, hasSecond, hasThird := false, false, false
for _, num := range nums {
// 先对有效名次去重,再进行插入排序
seen := (hasFirst && num == first) ||
(hasSecond && num == second) ||
(hasThird && num == third)
if seen {
continue
}
if !hasFirst || num > first {
// 从后往前移动名次,数值和存在标记一起更新
third = second
hasThird = hasSecond
second = first
hasSecond = hasFirst
first = num
hasFirst = true
} else if !hasSecond || num > second {
// 从后往前移动名次,数值和存在标记一起更新
third = second
hasThird = hasSecond
second = num
hasSecond = true
} else if !hasThird || num > third {
third = num
hasThird = true
}
}
if hasThird {
return third
}
return first
}
复杂度分析
- 时间复杂度:$O(n)$,每个元素只与三个候选进行固定次数的比较。
- 空间复杂度:$O(1)$,仅保存三个值和三个存在标记,不修改输入。
关键点总结
[!green]
- 排名按不同值计算,更新名次之前先去重。
- 插入新候选时从后向前顺延,数值与有效性一起移动。
- 三个名次足以维护目标,较小的已舍弃值以后也不会进入前三。
易错点总结
[!yellow]
- 只与第一名比较重复,会让重复第二名占据第三名。
- 只移动数值却不移动存在标记,可能明明已有三个不同值却仍判断第三名不存在。
- 用某个合法整数极值表示空名次,会与实际输入混淆;这里用布尔标记区分。
- 更新第一名后再用它给第二名赋值,会把旧第一名覆盖掉,导致两个名次相同。
- 不足三个不同值时返回第二名或最小的已有候选,不符合题目要求的最大值。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 215. 数组中的第K个最大元素 | 中等 | 原题按元素重数求第k大,本题先按不同值排名,并在不足三种值时返回最大值。 |
| 补充题 151. 字符串中最大的三个不同整数 | 简单 | 同样只维护三个不同最大候选,变形题额外从文本解析大整数并返回三个值。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!