LeetCode第268题_丢失的数字

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 中的所有数字都 独一无二
  • 进阶:你能否实现线性时间复杂度、仅使用额外常数空间的算法解决此问题?

解题思路

针对本题,我们可以采用多种方法来解决,以下是常用的三种解法:

方法一:数学求和法(推荐)

核心思想

  • 利用高斯求和公式计算理论总和
  • 计算实际总和,两者之差即为缺失的数字

具体步骤

  1. 计算完整序列 [0,n] 的和:expectedSum = n*(n+1)/2
  2. 遍历数组计算实际元素的和:actualSum
  3. 返回理论总和与实际总和的差值:expectedSum - actualSum

复杂度分析

  • 时间复杂度:O(n),只需要遍历一次数组
  • 空间复杂度:O(1),只使用常数额外空间

方法二:位运算法(异或)

核心思想

  • 利用异或运算的特性:a ^ a = 0a ^ 0 = a
  • 异或运算具有交换律和结合律

具体步骤

  1. 初始化结果变量为数组长度n
  2. 对于每个索引i和对应值nums[i],执行result ^= i ^ nums[i]
  3. 最终结果即为缺失的数字

复杂度分析

  • 时间复杂度:O(n),只需要遍历一次数组
  • 空间复杂度:O(1),只使用常数额外空间

方法三:排序法

核心思想

  • 将数组排序后比较索引和值
  • 第一个不匹配的位置对应的索引即为缺失的数字

具体步骤

  1. 对数组进行排序
  2. 遍历排序后的数组,比较nums[i]与i
  3. 如果不相等,返回i;否则继续遍历
  4. 如果全部匹配,返回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 = 3result = 3初始化result为n
第一步i=0, nums[0]=3result ^= 0 ^ 3 = 3 ^ 0 ^ 3 = 0索引0与值3异或
第二步i=1, nums[1]=0result ^= 1 ^ 0 = 0 ^ 1 ^ 0 = 1索引1与值0异或
第三步i=2, nums[2]=1result ^= 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 ms17.9 MB最优性能,避免溢出风险
C++数学求和法16 ms17.8 MB简单高效,易于理解
Python数学求和法32 ms15.9 MB内置sum函数高效
C#位运算法96 ms42.1 MB性能良好,逻辑巧妙

代码亮点

  1. 🎯 三种解法各有特点:位运算法最优雅,数学法最直观,排序法最简单
  2. 💡 位运算法利用异或运算的抵消特性,非常巧妙且避免溢出
  3. 🔍 数学求和法直接应用高斯公式,体现数学之美
  4. 🎨 代码结构清晰,各种方法独立实现,便于对比学习

常见错误分析

  1. 🚫 忽略边界情况,如数组为空或只有一个元素的处理
  2. 🚫 数学求和法可能出现整型溢出,需要使用long类型避免
  3. 🚫 排序后没有正确处理最后一个元素的情况(所有前面元素都匹配时)
  4. 🚫 没有考虑0可能是缺失数字的特殊情况

解法对比

解法时间复杂度空间复杂度优点缺点
数学求和法O(n)O(1)简单直观,易于理解和实现大数据时可能存在整型溢出风险
位运算法O(n)O(1)最优解,不存在溢出风险,逻辑巧妙理解难度较高,需要掌握异或特性
排序法O(n log n)O(1)思路简单,实现容易时间复杂度较高,不是最优解

相关题目


📖 系列导航

🔥 算法专题合集 - 查看完整合集

📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新第268题。


💬 互动交流

感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。

如果这篇文章对你有帮助,请:

  • 👍 点个赞,让更多人看到这篇文章
  • 📁 收藏文章,方便后续查阅复习
  • 🔔 关注作者,获取更多高质量算法题解
  • 💭 评论区留言,分享你的解题思路或提出疑问

你的支持是我持续分享的动力!

💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值