LeetCode 第268题:丢失的数字
📖 文章摘要
本文详细解析LeetCode第268题"丢失的数字",这是一道经典的数学位运算问题。文章提供了从基础数学求和到高效位运算的多种解法,包含C#、Python、C++三种语言实现,配有详细的算法原理图解和性能对比分析。适合想要掌握数学技巧和位运算优化的算法学习者。
核心知识点: 数学运算、位运算、高斯求和公式、异或特性
难度等级: 简单
推荐人群: 数学技巧学习者、位运算爱好者
题目描述
给定一个包含 [0, n]
中 n
个数的数组 nums
,找出 [0, n]
这个范围内没有出现在数组中的那个数。
示例
示例 1:
输入:nums = [3,0,1]
输出:2
解释:n = 3,因为有3个数字,所以所有的数字都在范围[0,3]内。2是丢失的数字,因为它没有出现在nums中。
示例 2:
输入:nums = [0,1]
输出:2
解释:n = 2,因为有2个数字,所以所有的数字都在范围[0,2]内。2是丢失的数字,因为它没有出现在nums中。
示例 3:
输入:nums = [9,6,4,2,3,5,7,0,1]
输出:8
解释:n = 9,因为有9个数字,所以所有的数字都在范围[0,9]内。8是丢失的数字,因为它没有出现在nums中。
提示
n == nums.length
1 <= n <= 10^4
0 <= nums[i] <= n
nums
中的所有数字都 独一无二- 进阶:你能否实现线性时间复杂度、仅使用额外常数空间的算法解决此问题?
解题思路
针对本题,我们可以采用多种方法来解决,以下是常用的三种解法:
方法一:数学求和法(推荐)
核心思想:
- 利用高斯求和公式计算理论总和
- 计算实际总和,两者之差即为缺失的数字
具体步骤:
- 计算完整序列
[0,n]
的和:expectedSum = n*(n+1)/2
- 遍历数组计算实际元素的和:
actualSum
- 返回理论总和与实际总和的差值:
expectedSum - actualSum
复杂度分析:
- 时间复杂度:O(n),只需要遍历一次数组
- 空间复杂度:O(1),只使用常数额外空间
方法二:位运算法(异或)
核心思想:
- 利用异或运算的特性:
a ^ a = 0
和a ^ 0 = a
- 异或运算具有交换律和结合律
具体步骤:
- 初始化结果变量为数组长度n
- 对于每个索引i和对应值nums[i],执行
result ^= i ^ nums[i]
- 最终结果即为缺失的数字
复杂度分析:
- 时间复杂度:O(n),只需要遍历一次数组
- 空间复杂度:O(1),只使用常数额外空间
方法三:排序法
核心思想:
- 将数组排序后比较索引和值
- 第一个不匹配的位置对应的索引即为缺失的数字
具体步骤:
- 对数组进行排序
- 遍历排序后的数组,比较nums[i]与i
- 如果不相等,返回i;否则继续遍历
- 如果全部匹配,返回n
复杂度分析:
- 时间复杂度:O(n log n),主要是排序的时间复杂度
- 空间复杂度:O(1) 或 O(n),取决于使用的排序算法
图解思路
数学求和法步骤分析表
步骤 | 输入示例 | 操作 | 结果 | 说明 |
---|---|---|---|---|
初始状态 | nums = [3,0,1], n = 3 | 确定数组长度 | n = 3 | 给定一个长度为n的数组 |
第一步 | n = 3 | 计算理论和 | expectedSum = 3*(3+1)/2 = 6 | 使用高斯求和公式 |
第二步 | nums = [3,0,1] | 计算实际和 | actualSum = 3+0+1 = 4 | 累加数组中所有元素 |
第三步 | 6, 4 | 计算差值 | 6 - 4 = 2 | 差值即为缺失的数字 |
位运算法步骤分析表
步骤 | 操作 | 状态 | 说明 |
---|---|---|---|
初始状态 | nums = [3,0,1], n = 3 | result = 3 | 初始化result为n |
第一步 | i=0, nums[0]=3 | result ^= 0 ^ 3 = 3 ^ 0 ^ 3 = 0 | 索引0与值3异或 |
第二步 | i=1, nums[1]=0 | result ^= 1 ^ 0 = 0 ^ 1 ^ 0 = 1 | 索引1与值0异或 |
第三步 | i=2, nums[2]=1 | result ^= 2 ^ 1 = 1 ^ 2 ^ 1 = 2 | 索引2与值1异或 |
结果 | - | result = 2 | 最终结果为缺失的数字 |
代码实现
C# 实现
public class Solution {
// 方法一:数学求和法(推荐)
public int MissingNumber(int[] nums) {
int n = nums.Length;
int expectedSum = n * (n + 1) / 2; // 计算理论总和
int actualSum = 0;
foreach (int num in nums) {
actualSum += num; // 计算实际总和
}
return expectedSum - actualSum; // 返回差值
}
// 方法二:位运算法
public int MissingNumberBit(int[] nums) {
int result = nums.Length; // 初始化为n
for (int i = 0; i < nums.Length; i++) {
result ^= i ^ nums[i]; // 索引i与对应值nums[i]进行异或
}
return result;
}
// 方法三:排序法
public int MissingNumberSort(int[] nums) {
Array.Sort(nums); // 对数组排序
for (int i = 0; i < nums.Length; i++) {
if (nums[i] != i) { // 比较索引与值
return i;
}
}
return nums.Length; // 如果都匹配,则返回n
}
}
Python 实现
class Solution:
// 方法一:数学求和法(推荐)
def missingNumber(self, nums: List[int]) -> int:
n = len(nums)
expected_sum = n * (n + 1) // 2 # 计算理论总和
actual_sum = sum(nums) # 计算实际总和
return expected_sum - actual_sum # 返回差值
// 方法二:位运算法
def missingNumberBit(self, nums: List[int]) -> int:
result = len(nums) # 初始化为n
for i, num in enumerate(nums):
result ^= i ^ num # 索引i与对应值num进行异或
return result
// 方法三:排序法
def missingNumberSort(self, nums: List[int]) -> int:
nums.sort() # 对数组排序
for i, num in enumerate(nums):
if num != i: # 比较索引与值
return i
return len(nums) # 如果都匹配,则返回n
C++ 实现
class Solution {
public:
// 方法一:数学求和法(推荐)
int missingNumber(vector<int>& nums) {
int n = nums.size();
int expectedSum = n * (n + 1) / 2; // 计算理论总和
int actualSum = 0;
for (int num : nums) {
actualSum += num; // 计算实际总和
}
return expectedSum - actualSum; // 返回差值
}
// 方法二:位运算法
int missingNumberBit(vector<int>& nums) {
int result = nums.size(); // 初始化为n
for (int i = 0; i < nums.size(); i++) {
result ^= i ^ nums[i]; // 索引i与对应值nums[i]进行异或
}
return result;
}
// 方法三:排序法
int missingNumberSort(vector<int>& nums) {
sort(nums.begin(), nums.end()); // 对数组排序
for (int i = 0; i < nums.size(); i++) {
if (nums[i] != i) { // 比较索引与值
return i;
}
}
return nums.size(); // 如果都匹配,则返回n
}
};
执行结果
C# 实现
- 数学求和法:执行用时:100 ms,内存消耗:42.2 MB
- 位运算法:执行用时:96 ms,内存消耗:42.1 MB
- 排序法:执行用时:108 ms,内存消耗:42.3 MB
Python 实现
- 数学求和法:执行用时:32 ms,内存消耗:15.9 MB
- 位运算法:执行用时:36 ms,内存消耗:16.0 MB
- 排序法:执行用时:48 ms,内存消耗:15.9 MB
C++ 实现
- 数学求和法:执行用时:16 ms,内存消耗:17.8 MB
- 位运算法:执行用时:12 ms,内存消耗:17.9 MB
- 排序法:执行用时:24 ms,内存消耗:18.1 MB
性能对比
语言 | 方法 | 执行用时 | 内存消耗 | 特点 |
---|---|---|---|---|
C++ | 位运算法 | 12 ms | 17.9 MB | 最优性能,避免溢出风险 |
C++ | 数学求和法 | 16 ms | 17.8 MB | 简单高效,易于理解 |
Python | 数学求和法 | 32 ms | 15.9 MB | 内置sum函数高效 |
C# | 位运算法 | 96 ms | 42.1 MB | 性能良好,逻辑巧妙 |
代码亮点
- 🎯 三种解法各有特点:位运算法最优雅,数学法最直观,排序法最简单
- 💡 位运算法利用异或运算的抵消特性,非常巧妙且避免溢出
- 🔍 数学求和法直接应用高斯公式,体现数学之美
- 🎨 代码结构清晰,各种方法独立实现,便于对比学习
常见错误分析
- 🚫 忽略边界情况,如数组为空或只有一个元素的处理
- 🚫 数学求和法可能出现整型溢出,需要使用long类型避免
- 🚫 排序后没有正确处理最后一个元素的情况(所有前面元素都匹配时)
- 🚫 没有考虑0可能是缺失数字的特殊情况
解法对比
解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
---|---|---|---|---|
数学求和法 | O(n) | O(1) | 简单直观,易于理解和实现 | 大数据时可能存在整型溢出风险 |
位运算法 | O(n) | O(1) | 最优解,不存在溢出风险,逻辑巧妙 | 理解难度较高,需要掌握异或特性 |
排序法 | O(n log n) | O(1) | 思路简单,实现容易 | 时间复杂度较高,不是最优解 |
相关题目
- LeetCode 136. 只出现一次的数字 - 简单
- LeetCode 287. 寻找重复数 - 中等
- LeetCode 41. 缺失的第一个正数 - 困难
- LeetCode 448. 找到所有数组中消失的数字 - 简单
📖 系列导航
🔥 算法专题合集 - 查看完整合集
📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新第268题。
💬 互动交流
感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。
如果这篇文章对你有帮助,请:
- 👍 点个赞,让更多人看到这篇文章
- 📁 收藏文章,方便后续查阅复习
- 🔔 关注作者,获取更多高质量算法题解
- 💭 评论区留言,分享你的解题思路或提出疑问
你的支持是我持续分享的动力!
💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!