mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
1563 字
4 分钟
第二大元素问题
2026-06-10 12:45:07

1. 问题描述#

第二大元素问题是数组中的一个经典基础问题。

给定一个整数数组,要求找出其中的第二大元素。例如:

nums=[3,1,5,2,4]nums = [3, 1, 5, 2, 4]

数组中最大的元素是 55,第二大的元素是 44

虽然这个问题看起来很简单,但实际写代码时要先弄清楚一个关键点:

第二大元素到底是”严格第二大”,还是”排序后的第二个元素”?

这两种题型的答案可能不一样。


2. 两种常见题型#

2.1 严格第二大元素#

严格第二大指的是:

在不同数值中,找出仅次于最大值的那个数。

例如:

nums=[5,5,4,3]nums = [5, 5, 4, 3]

最大值是 55,但因为重复的 55 不能再算作第二大,所以严格第二大是 44

这种题型更关注”不同数字之间的大小关系”。

2.2 排序意义上的第二大元素#

排序意义上的第二大指的是:

将数组从大到小排序后,排在第二个位置的元素。

例如:

nums=[5,5,4,3]nums = [5, 5, 4, 3]

从大到小排序后仍然是 [5,5,4,3][5, 5, 4, 3],此时第二个位置是 55,所以排序意义上的第二大就是 55

这种题型允许重复元素参与排名。

小结

写代码之前,先看清题目要求的是哪一种”第二大”。严格第二大需要去重,排序第二大允许重复。


3. 一次遍历解法(严格第二大)#

对于严格第二大元素,可以维护两个变量:maxmax 记录当前最大值,secondsecond 记录当前第二大值。

遍历数组时分三种情况:

  1. 如果当前元素 num>maxnum > max,说明出现了新的最大值。此时原来的 maxmax 变成第二大,numnum 成为新的最大值。
  2. 如果当前元素 num<maxnum < max,并且 num>secondnum > second,说明它不是最大值,但比当前第二大更大,于是更新 secondsecond
  3. 如果当前元素等于 maxmax,或者不超过 secondsecond,则跳过。

用前面的例子走一遍:

nums=[3,1,5,2,4]nums = [3, 1, 5, 2, 4]
初始:max = null, second = null
遇到 3:max = 3
遇到 1:second = 1
遇到 5:second = 3, max = 5
遇到 2:不更新(2 < second = 3)
遇到 4:second = 4

最终第二大元素是 44

Java 代码如下:

class Solution {
public int secondLargest(int[] nums) {
if (nums == null || nums.length < 2) {
throw new IllegalArgumentException("数组长度至少为 2");
}
Integer max = null;
Integer second = null;
for (int num : nums) {
// 出现新的最大值:原来的 max 退为第二大
if (max == null || num > max) {
second = max;
max = num;
// num 必须严格小于 max,才能作为严格第二大的候选
} else if (num < max && (second == null || num > second)) {
second = num;
}
}
// 如果没有找到不同于 max 的数,说明严格第二大不存在
if (second == null) {
throw new IllegalArgumentException("不存在严格第二大元素");
}
return second;
}
}
  • 时间复杂度O(n)O(n),只需要遍历一次数组。
  • 空间复杂度O(1)O(1),只用了两个额外变量。

4. 分治法解第二大元素问题#

第二大元素问题也可以用分治法解决。

实际做题时,一次遍历通常更简单、空间也更优。这里继续讲分治法,主要是为了理解递归拆分、结果合并和候选淘汰思想。

分治法的核心思想是:

:把原问题拆成规模更小的子问题;

:分别求解子问题;

:把子问题的答案合并成原问题的答案。

放到第二大元素问题中,就是:

  1. 把数组分成左右两半;
  2. 分别求出左半部分的最大值和第二大值;
  3. 分别求出右半部分的最大值和第二大值;
  4. 将左右两边的结果合并,得到整个数组的最大值和第二大值。

4.1 合并过程举例#

例如数组:

nums=[3,1,5,2,4,8,6]nums = [3, 1, 5, 2, 4, 8, 6]

可以拆成左右两部分:

左半部分:[3,1,5]右半部分:[2,4,8,6]\text{左半部分:}[3, 1, 5]\text{,}\quad \text{右半部分:}[2, 4, 8, 6]

左半部分的结果是 left.max=5left.max = 5left.second=3left.second = 3

右半部分的结果是 right.max=8right.max = 8right.second=6right.second = 6

现在要把左右两部分合并。因为 right.max=8>left.max=5right.max = 8 > left.max = 5,所以整体最大值是 88

那么整体第二大元素只可能来自两个候选:left.max=5left.max = 5right.second=6right.second = 6

所以整体第二大是:

max(5,6)=6\max(5, 6) = 6

4.2 为什么不用考虑 left.second?#

在上面的例子中,left.second=3left.second = 3 为什么不需要参与全局第二大的竞争?

原因很直接:left.secondleft.maxleft.second \le left.max,而 left.maxleft.max 已经在竞争全局第二大了。一个比候选者还小的值,不可能成为全局第二大。

这就是一种”候选淘汰”思想:

如果某个候选值已经被同一组里更大的值压住了,那么它就没有必要继续参与全局比较。


5. 分治法 Java 实现#

class Solution {
static class Pair {
int max;
Integer second;
Pair(int max, Integer second) {
this.max = max;
this.second = second;
}
}
public int secondLargest(int[] nums) {
if (nums == null || nums.length < 2) {
throw new IllegalArgumentException("数组长度至少为 2");
}
Pair ans = divide(nums, 0, nums.length - 1);
if (ans.second == null) {
throw new IllegalArgumentException("不存在严格第二大元素");
}
return ans.second;
}
private Pair divide(int[] nums, int left, int right) {
// 单个元素区间:只有最大值,还不存在严格第二大
if (left == right) {
return new Pair(nums[left], null);
}
int mid = left + (right - left) / 2;
// 分别求左右子数组的最大值和第二大值
Pair leftPair = divide(nums, left, mid);
Pair rightPair = divide(nums, mid + 1, right);
// 合并左右子问题的结果
return merge(leftPair, rightPair);
}
private Pair merge(Pair a, Pair b) {
if (a.max > b.max) {
// a 贡献全局最大值,第二大只能来自 a.second 或 b.max
return new Pair(a.max, maxNullable(a.second, b.max));
} else if (a.max < b.max) {
// b 贡献全局最大值,第二大只能来自 a.max 或 b.second
return new Pair(b.max, maxNullable(a.max, b.second));
} else {
// 两边最大值相同,不能把重复最大值当成严格第二大
return new Pair(a.max, maxNullable(a.second, b.second));
}
}
private Integer maxNullable(Integer a, Integer b) {
// null 表示当前还没有严格第二大候选
if (a == null) {
return b;
}
if (b == null) {
return a;
}
return Math.max(a, b);
}
}

这里用包装类型 Integer 表示 second,是为了区分两种情况:

  • second == null:当前区间里还不存在严格第二大元素。
  • second 有值:这个值就是当前区间的严格第二大元素。

不要用 Integer.MIN_VALUE 这类具体数值当作“不存在”的标记,因为数组元素本身也可能等于 Integer.MIN_VALUE

最能体现分治思想的是这一段:

Pair leftPair = divide(nums, left, mid);
Pair rightPair = divide(nums, mid + 1, right);
return merge(leftPair, rightPair);

先分别解决左右两个子数组的问题,再把两个子问题的答案合并成整体答案。


6. 复杂度分析#

每个元素在递归过程中都会被处理一次,合并时只进行常数次比较。所以时间复杂度为 O(n)O(n)

由于递归调用会产生函数调用栈,递归深度为 O(logn)O(\log n),所以空间复杂度为 O(logn)O(\log n)

解法时间复杂度空间复杂度
一次遍历O(n)O(n)O(1)O(1)
分治法O(n)O(n)O(logn)O(\log n)

可以看到,两种解法的时间复杂度都是 O(n)O(n)。一次遍历在空间上更优,而分治法的价值在于它展示了一种通用的问题拆解思路,这种思路可以推广到更复杂的问题中。


7. 总结#

第二大元素问题看似简单,但要先区分题型:

  • 如果要求”严格第二大”,重复的最大值不能算第二大。
  • 如果要求”排序意义上的第二大”,重复元素可以参与排名。

常规做法是一次遍历,维护 maxmaxsecondsecond,时间 O(n)O(n),空间 O(1)O(1)

分治做法则是把数组拆成左右两部分,分别求出每部分的最大值和第二大值,再通过合并得到整体结果。合并时的关键是:

哪一边贡献了全局最大值,就考虑那一边的第二大;另一边只考虑最大值。

这就是第二大元素问题中分治合并的核心。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

第二大元素问题
https://dawn114514.site/posts/algorithm/secondlargestelement/
作者
黎明
发布于
2026-06-10 12:45:07
许可协议
MIT

部分信息可能已经过时

目录