获取数组中第二大的唯一值应使用distinct().orderbydescending(x=>x).skip(1).firstordefault(),兼顾去重、降序、空安全与简洁性。

用 OrderByDescending + Skip + First 最快写出来
多数人第一反应是排序再取索引 1,但直接调用 OrderByDescending 会新建数组、触发完整排序——其实只要第二大的值,没必要全排完。LINQ 的 Skip(1).First() 组合能少做不少事,尤其对大数组更明显。
常见错误现象:arr.OrderBy(x => x).Skip(arr.Length - 2).First() 写反方向,结果拿到倒数第二小;或没处理重复值,比如 {5,5,4,3,2} 里第二大的其实是 4,不是 5。
- 必须用
OrderByDescending,不是OrderBy - 重复值要跳过:先
Distinct(),再Skip(1).First() - 数组长度不足 2 时会抛
InvalidOperationException,得提前检查
示例:
var secondLargest = arr.Distinct().OrderByDescending(x => x).Skip(1).FirstOrDefault();注意用了
FirstOrDefault 而非 First,避免空序列崩溃。不用 LINQ 时怎么手写一次遍历
如果项目禁用 LINQ、或对性能极度敏感(比如嵌入式场景),就得手动扫一遍。核心思路是维护两个变量:largest 和 secondLargest,边扫边更新——时间复杂度 O(n),空间 O(1),且天然去重。
容易踩的坑:secondLargest 初始值设成 int.MinValue 看似合理,但遇到全是负数且最大值就是 int.MinValue 时会出错;还有人把更新逻辑写成“如果比 largest 小就更新 second”,漏掉了“比 largest 小但比 current second 大”的分支。
- 初始化用
long.MinValue或直接用数组前两元素推导,别硬写死最小值 - 更新顺序很重要:先更新
secondLargest(当值介于两者之间),再更新largest(当值更大) - 遇到相等值直接跳过,不参与比较
示例关键片段:
long largest = Math.Max(arr[0], arr[1]);<br>long secondLargest = Math.Min(arr[0], arr[1]);<br>for (int i = 2; i if (arr[i] > largest) {<br> secondLargest = largest;<br> largest = arr[i];<br> } else if (arr[i] > secondLargest && arr[i] secondLargest = arr[i];<br> }<br>}
Array.Sort 后取索引 1 的隐患
有人图省事直接 Array.Sort(arr, (a,b) => b.CompareTo(a)); return arr[1];,看起来短,但有三个硬伤:破坏原数组、没去重、没边界保护。
使用场景受限:只适合临时数组、且确认长度 ≥ 2、且允许修改原数据。一旦 arr 是别人传来的引用,或者后续还要用原始顺序,这就埋了雷。
-
Array.Sort是 in-place 排序,原数组顺序永久丢失 - 重复最大值(如
{9,9,8,7})会导致arr[1]还是 9,不是想要的 8 - 若数组只有 1 个元素,
arr[1]直接IndexOutOfRangeException
真要用排序法,至少得:
var copy = (int[])arr.Clone();<br>Array.Sort(copy, (a,b) => b.CompareTo(a));<br>var distinctCopy = copy.Distinct().ToArray();<br>return distinctCopy.Length > 1 ? distinctCopy[1] : throw new InvalidOperationException();但这就绕回 LINQ 了,不如第一步直接用。
泛型数组和 null 值怎么处理
如果数组元素是 int? 或自定义类,Distinct 和比较逻辑会变脆弱。比如 null 参与比较会抛异常,OrderByDescending 默认把 null 排最前(升序)或最后(降序),但语义上“第二大”不该包含 null。
参数差异明显:值类型数组(int[])可直接比;引用类型必须提供 IComparer<t></t> 或用 CompareTo 显式判空;Nullable 类型得先 Where(x => x.HasValue) 过滤。
- 对
int?数组,先.Where(x => x.HasValue).Select(x => x.Value)再走主逻辑 - 自定义类必须实现
IComparable,否则OrderByDescending报ArgumentException - 用
Comparer<t>.Default</t>比手写 lambda 更安全,尤其涉及 null 时
示例过滤 null:
var validNumbers = arr.Where(x => x != null).Cast<int>().Distinct();</int>注意
Cast<int>()</int> 仅适用于非 null 元素,否则运行时报错。实际写的时候,绝大多数情况用第一种 LINQ 写法足够清晰可靠。真正卡性能或不能引用 System.Linq 的时候,才值得花精力手写单次遍历——但那个逻辑里“更新 second 的条件判断顺序”和“初始值选取”,多看两遍代码才能确保没漏 case。











