单次遍历O(n)可找出第二大的数,关键在于正确初始化max1和max2:需用实际元素初始化max1,max2设为未定义状态(如std::optional或哨兵值加标志),避免全负数或重复最大值出错。

直接遍历一次就能搞定,但要注意重复元素和边界情况
不用排序、不用额外容器,单次遍历 O(n) 时间就能找出第二大的数。关键不是“怎么写循环”,而是怎么初始化和更新两个变量——max1 和 max2。
常见错误是把 max2 初始化成 0 或 INT_MIN,结果数组全为负数或含重复最大值时出错。正确做法是用实际元素初始化,且保证 max2 初始为“未定义”状态(比如用 std::optional<int></int>,或设为一个不可能的哨兵值并加标志位)。
- 如果数组长度 std::invalid_argument)
- 先取前两个不同值来初始化
max1和max2;若全相同,则无第二大的数 - 后续每个元素只和
max1比:大于max1就更新两者;介于max1和max2之间才更新max2 - 跳过等于
max1的元素,否则重复最大值会错误地“挤掉”真正的第二大值
用 std::set 去重后取倒数第二个?小心性能和语义陷阱
std::set 自动排序且去重,取 std::prev(std::end(s), 2) 看似简洁,但代价明显:
- 时间复杂度升到
O(n log n),空间多用O(n) - 如果题目允许重复最大值(例如
[5,5,4,3]中第二大的是 4),std::set没问题;但如果要求“第二大的出现位置”或“第二大的原始值(不去重)”,它就完全跑偏了 - 空
set或仅一个元素时,std::prev行为未定义,必须提前检查s.size() >= 2
简单场景可以写一行:
std::set<int> s(arr, arr + n); return s.size() < 2 ? -1 : *std::prev(s.end(), 2);,但别在面试或高频调用路径里这么干。
立即学习“C++免费学习笔记(深入)”;
用 std::nth_element?它不保证“第二大”的稳定含义
std::nth_element 能在平均 O(n) 内把第 k 小(或大)的元素放到正确位置,但注意:
- 它只保证索引
k处是目标值,前后不排序——所以nth_element(arr, arr + 1, arr + n, std::greater<int>())</int>理论上能拿到“第二大的数”,但前提是数组至少有两个不同值 - 如果所有元素相同,
arr[1]就是最大值本身,不是“第二大”(应视为不存在) - 它会修改原数组顺序,且对自定义类型需谨慎实现比较逻辑
- 比手写遍历多一层库调用开销,调试时也不如裸循环直观
容易被忽略的细节:相等、溢出、类型与 const 正确性
写 int max1 = arr[0], max2 = INT_MIN; 是典型隐患:
-
INT_MIN可能等于某个合法输入值(比如数组含INT_MIN且它是第二大),导致误判 - 没处理
arr[0] == arr[1]就进主循环,max2仍为INT_MIN,后续所有小于max1的数都会被当成候选,最终返回错误值 - 用
long long接收int数组?没必要,还可能掩盖隐式转换问题 - 输入是
const int*?函数参数该加const就加,别为了少写几个字符破坏接口契约
真正健壮的实现,往往从处理前三个元素开始判断初始关系,而不是硬编码前两个——边界越早理清,后面越省心。


















