摘要
这篇文章围绕 LeetCode 第 268 题“Missing Number”展开,讲的是如何从一个 [0, n]
的连续数字序列中,找出唯一缺失的那个数字。看上去像个数学题,但背后其实有不少技巧值得借鉴。我们将提供 Swift 解法,分析其背后的思路,并结合日常场景,聊聊为什么这种“对照差值”的方法在开发中非常常用。
描述
题目是这样说的:
你有一个长度为 n
的数组,里面是 [0, n]
之间的 n
个唯一整数。也就是说,总共应该有 n+1
个数字,但你手里只有 n
个,丢了一个。现在需要你把这个缺失的数字找出来。
举几个例子:
输入:nums = [3, 0, 1]
输出:2
输入:nums = [0, 1]
输出:2
输入:nums = [9,6,4,2,3,5,7,0,1]
输出:8
这个题看起来挺“面试风”的,确实很多公司都会问这类数字序列类问题。它考的不是你写了多少行代码,而是你对“序列”这件事有没有底层理解。
题解答案
我们先贴一下 Swift 解法:
func missingNumber(_ nums: [Int]) -> Int {
let n = nums.count
let expectedSum = n * (n + 1) / 2
let actualSum = nums.reduce(0, +)
return expectedSum - actualSum
}
是不是看上去特别短?但其实这行代码里面的数学逻辑非常硬核,我们下面来拆解。
题解代码分析
用数学做“对照”
我们要找 [0, n]
中丢失的数字,假设这些数字全都在数组里,理论上它们的总和是 0 + 1 + 2 + ... + n
,也就是:
n * (n + 1) / 2
这是一个非常经典的等差数列求和公式。如果我们把这个“应有的总和”减去我们实际拿到的 nums
数组中所有数字的总和,差值自然就是“丢的那一个”。
比如 nums = [3, 0, 1]
:
- 应有总和是:
3 * (3 + 1) / 2 = 6
- 实际总和是:
3 + 0 + 1 = 4
- 差值是:
6 - 4 = 2
,也就是丢失的数字。
为什么用 reduce?
在 Swift 中,nums.reduce(0, +)
是一种非常常用的写法,等价于把数组里所有元素都加起来。比用 for
循环累加更清爽。
示例测试及结果
我们可以写一段简单的 Demo 来验证这段代码的正确性:
func testMissingNumber() {
print(missingNumber([3, 0, 1])) // 输出:2
print(missingNumber([0, 1])) // 输出:2
print(missingNumber([9,6,4,2,3,5,7,0,1])) // 输出:8
print(missingNumber([1])) // 输出:0
print(missingNumber([0])) // 输出:1
}
testMissingNumber()
输出结果如下:
2
2
8
0
1
全部正确,说明逻辑没问题,代码也可以直接上手跑。
时间复杂度
遍历数组来计算总和的操作是一次性完成的,所以时间复杂度是:
O(n)
其中 n
是数组的长度。
空间复杂度
我们没有额外开数组或数据结构来做辅助存储,变量是常数个,所以空间复杂度是:
O(1)
这也是这道题的加分点——既快又省空间。
日常场景中的应用
别看这个题小,其实这种“差值求缺项”的方式,在日常开发里非常常见:
1. 报表对账
比如一个电商平台,每天会导出订单总数和财务总数,你用一对 SQL 算出来的数据总和跟系统报表一比,如果总和不一致,就能快速定位出“哪笔订单跑丢了”。
2. 枚举检测
在游戏开发中,有时候你给某个角色设计了编号为 0~50
的技能池,一旦某个编号技能丢失,系统可能会崩。写段检测代码来找出哪个技能配置没上线,用的就是这个思路。
3. 序列校验
在某些通信协议中,一段数据包传过来,每个包都有顺序编号,如果中间漏了一个编号,系统就要做容错或重发。快速检测丢失编号,也是这个逻辑。
总结
这道题背后其实考察的是“对数据整体结构的把握”。不是让你硬干暴力搜索,而是看你能不能抽象问题,用数学规律做“信息对照”。
如果你平时在做日志对账、ID序列校验、订单缺失检测等问题,不妨考虑一下类似的思路 —— 先想一想“我期望得到什么”,再和实际结果做差。