LeetCode100天Day8-缺失数字与只出现一次的数字

LeetCode100天Day8-缺失数字与只出现一次的数字:排序与找数技巧

摘要:本文详细解析了LeetCode中两道查找类题目------"缺失数字"和"只出现一次的数字"。通过排序后逐个比较的方法找缺失数字,以及排序后通过索引关系找只出现一次的元素,帮助读者掌握排序在查找问题中的应用。

目录

文章目录

  • LeetCode100天Day8-缺失数字与只出现一次的数字:排序与找数技巧
    • 目录
    • [1. 缺失数字(Missing Number)](#1. 缺失数字(Missing Number))
      • [1.1 题目描述](#1.1 题目描述)
      • [1.2 解题思路](#1.2 解题思路)
      • [1.3 代码实现](#1.3 代码实现)
      • [1.4 代码逐行解释](#1.4 代码逐行解释)
      • [1.5 执行流程详解](#1.5 执行流程详解)
      • [1.6 算法图解](#1.6 算法图解)
      • [1.7 复杂度分析](#1.7 复杂度分析)
      • [1.8 边界情况](#1.8 边界情况)
    • [2. 只出现一次的数字(Single Number)](#2. 只出现一次的数字(Single Number))
    • [3. 两题对比与总结](#3. 两题对比与总结)
      • [3.1 共同点](#3.1 共同点)
      • [3.2 排序在查找中的应用](#3.2 排序在查找中的应用)
      • [3.3 索引与值的关系](#3.3 索引与值的关系)
      • [3.4 查找算法对比](#3.4 查找算法对比)
    • [4. 总结](#4. 总结)
    • 参考资源
    • 文章标签

1. 缺失数字(Missing Number)

1.1 题目描述

给定一个包含 [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 中。

1.2 解题思路

这道题的核心思想是:将数组排序后,逐个检查每个位置的值是否等于其索引。

解题步骤:

  1. 对数组进行排序
  2. 从0开始遍历,检查每个位置
  3. 如果当前值不等于索引,说明这个索引就是缺失的数字
  4. 如果遍历完都没找到,说明缺失的是最后一个数

1.3 代码实现

java 复制代码
import java.util.*;

class Solution {
    public int missingNumber(int[] nums) {
        Arrays.sort(nums);
        int temp = 0;

        for(int i = 0; i < nums.length; i++){
            if(temp != nums[i]){
                return temp;
            }
            temp++;
        }

        return temp;
    }
}

1.4 代码逐行解释

第一部分:排序
java 复制代码
Arrays.sort(nums);

排序作用:

排序前 排序后
[3,0,1] [0,1,3]
[9,6,4,2,3,5,7,0,1] [0,1,2,3,4,5,6,7,9]

排序后,元素按从小到大排列,缺失的数字会在数组中形成"断层"。

第二部分:查找缺失数字
java 复制代码
int temp = 0;
for(int i = 0; i < nums.length; i++){
    if(temp != nums[i]){
        return temp;
    }
    temp++;
}

查找逻辑:

复制代码
期望:[0, 1, 2, 3, ..., n]
实际:[nums[0], nums[1], nums[2], ..., nums[n-1]]

逐个比较:
位置0:期望0,实际nums[0]
位置1:期望1,实际nums[1]
位置2:期望2,实际nums[2]
...

当temp != nums[i]时,说明temp这个数字在数组中缺失。

第三部分:返回结果
java 复制代码
return temp;

1.5 执行流程详解

示例1 :nums = [3,0,1]

复制代码
步骤1:排序
排序后:[0, 1, 3]

步骤2:查找
i=0: temp=0, nums[0]=0, 相等,temp++ → 1
i=1: temp=1, nums[1]=1, 相等,temp++ → 2
i=2: temp=2, nums[2]=3, 不相等,返回 2

输出:2

示例2 :nums = [0,1]

复制代码
步骤1:排序
排序后:[0, 1]

步骤2:查找
i=0: temp=0, nums[0]=0, 相等,temp++ → 1
i=1: temp=1, nums[1]=1, 相等,temp++ → 2

循环结束,返回 temp = 2

输出:2

示例3 :nums = [9,6,4,2,3,5,7,0,1]

复制代码
步骤1:排序
排序后:[0, 1, 2, 3, 4, 5, 6, 7, 9]

步骤2:查找
i=0: temp=0, nums[0]=0, 相等,temp++ → 1
i=1: temp=1, nums[1]=1, 相等,temp++ → 2
i=2: temp=2, nums[2]=2, 相等,temp++ → 3
i=3: temp=3, nums[3]=3, 相等,temp++ → 4
i=4: temp=4, nums[4]=4, 相等,temp++ → 5
i=5: temp=5, nums[5]=5, 相等,temp++ → 6
i=6: temp=6, nums[6]=6, 相等,temp++ → 7
i=7: temp=7, nums[7]=7, 相等,temp++ → 8
i=8: temp=8, nums[8]=9, 不相等,返回 8

输出:8

1.6 算法图解

复制代码
示例:nums = [0, 1, 3]

索引:  0   1   2
值:    0   1   3
期望:  0   1   2
        ↓   ↓   ↓
比较:  =   =   ≠
                ↑
            缺失的数字

结果:返回 2

1.7 复杂度分析

分析维度 复杂度 说明
时间复杂度 O(n log n) 排序O(n log n) + 遍历O(n)
空间复杂度 O(1) Java的Arrays.sort使用原地排序

1.8 边界情况

输入 说明 输出
[0] 缺失1 1
[1] 缺失0 0
[0,1] 缺失2 2
[1,2] 缺失0 0

2. 只出现一次的数字(Single Number)

2.1 题目描述

给你一个非空整数数组 nums,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

示例 1:

复制代码
输入:nums = [2,2,1]
输出:1

示例 2:

复制代码
输入:nums = [4,1,2,1,2]
输出:4

示例 3:

复制代码
输入:nums = [1]
输出:1

2.2 解题思路

通过排序后,相同的元素会相邻。利用这一特性,可以逐对检查元素:

解题思路:

  1. 对数组排序
  2. 从索引0开始,每次检查两个元素(i和i+1)
  3. 如果numsi != numsi+1,说明numsi是只出现一次的元素
  4. 如果遍历完偶数索引都没找到,说明最后一个元素是答案

2.3 代码实现

java 复制代码
import java.util.*;

class Solution {
    public int singleNumber(int[] nums) {
        Arrays.sort(nums);

        for(int i = 0; i < nums.length; i++){
            if(i % 2 == 1){
                // 奇数索引,应该与前一个元素相等
                if(nums[i] != nums[i-1]){
                    return nums[i-1];
                }
            }
        }

        return nums[nums.length-1];
    }
}

2.4 代码逐行解释

第一部分:排序
java 复制代码
Arrays.sort(nums);

排序效果:

排序前 排序后
[2,2,1] [1,2,2]
[4,1,2,1,2] [1,1,2,2,4]

排序后,相同的元素会相邻。

第二部分:遍历查找
java 复制代码
for(int i = 0; i < nums.length; i++){
    if(i % 2 == 1){
        if(nums[i] != nums[i-1]){
            return nums[i-1];
        }
    }
}

查找逻辑:

复制代码
正常情况(每对元素相同):
索引: 0   1   2   3   4
值:  [a,  a,  b,  b,  c]
       ↑   ↑   ↑   ↑   ↑
       偶  奇  偶  奇  偶

检查逻辑:
- 在奇数索引(1,3,5,...)检查是否与前一个偶数索引相等
- 如果不相等,说明前一个元素是单个的
第三部分:返回最后一个元素
java 复制代码
return nums[nums.length-1];

如果所有成对的元素都匹配,说明最后一个元素是单个的。

2.5 执行流程详解

示例1 :nums = [2,2,1]

复制代码
步骤1:排序
排序后:[1, 2, 2]

步骤2:查找
i=0: i%2=0(偶数),跳过检查
i=1: i%2=1(奇数),检查nums[1]与nums[0]
     nums[1]=2, nums[0]=1
     不相等,返回 nums[0] = 1

输出:1

示例2 :nums = [4,1,2,1,2]

复制代码
步骤1:排序
排序后:[1, 1, 2, 2, 4]

步骤2:查找
i=0: i%2=0,跳过
i=1: i%2=1,检查nums[1]与nums[0]
     nums[1]=1, nums[0]=1
     相等,继续
i=2: i%2=0,跳过
i=3: i%2=1,检查nums[3]与nums[2]
     nums[3]=2, nums[2]=2
     相等,继续
i=4: i%2=0,跳过

循环结束,返回 nums[4] = 4

输出:4

示例3 :nums = [1]

复制代码
步骤1:排序
排序后:[1]

步骤2:查找
i=0: i%2=0,跳过

循环结束,返回 nums[0] = 1

输出:1

2.6 算法图解

复制代码
情况1:单个元素在前面
排序后:[1, 2, 2, 3, 3]
索引:   0  1  2  3  4
         ↑  ↑
        不相等
         ↑
    返回 nums[0] = 1

情况2:单个元素在最后
排序后:[1, 1, 2, 2, 3]
索引:   0  1  2  3  4
                    ↑
                都匹配
                    ↑
            返回 nums[4] = 3

2.7 复杂度分析

分析维度 复杂度 说明
时间复杂度 O(n log n) 排序O(n log n) + 遍历O(n)
空间复杂度 O(1) 原地排序

2.8 边界情况

输入 说明 输出
[1] 单个元素 1
[1,1,2] 单个在最后 2
[2,1,1] 单个在最前 2
[1,2,1,2,3] 单个在中间 3

3. 两题对比与总结

3.1 共同点

对比项 缺失数字 只出现一次的数字
核心技巧 排序后比较 排序后比较
时间复杂度 O(n log n) O(n log n)
空间复杂度 O(1) O(1)
排序作用 使元素有序 使相同元素相邻

3.2 排序在查找中的应用

排序的作用:

  1. 使元素有序,便于逐个比较
  2. 使相同元素相邻,便于成对检查
  3. 简化查找逻辑,降低算法复杂度

排序方法:

java 复制代码
// 1. Arrays.sort(快速排序)
Arrays.sort(array);

// 2. Arrays.sort(指定范围)
Arrays.sort(array, fromIndex, toIndex);

// 3. Collections.sort(用于List)
Collections.sort(list);

3.3 索引与值的关系

缺失数字:

java 复制代码
// 期望:nums[i] == i
// 实际:如果不等,i就是缺失的数字
for(int i = 0; i < nums.length; i++){
    if(nums[i] != i){
        return i;
    }
}

只出现一次的数字:

java 复制代码
// 期望:nums[i] == nums[i-1](i为奇数)
// 实际:如果不等,nums[i-1]就是单个的
for(int i = 0; i < nums.length; i++){
    if(i % 2 == 1 && nums[i] != nums[i-1]){
        return nums[i-1];
    }
}

3.4 查找算法对比

算法 时间复杂度 是否需要排序
线性查找 O(n) 否
二分查找 O(log n) 是
排序后查找 O(n log n) 是

对于这两道题:

  • 缺失数字可以用数学公式O(n)解决:sum(0到n) - sum(nums)
  • 只出现一次的数字可以用异或O(n)解决

4. 总结

今天我们学习了两道查找类题目:

  1. 缺失数字:通过排序后逐个比较,找到与索引不匹配的数字
  2. 只出现一次的数字:通过排序后成对检查,找到不成对的元素

核心收获:

  • 排序是解决查找问题的重要工具
  • 排序后的数组具有很多有用的性质
  • 利用索引与值的关系可以简化问题
  • 奇偶索引在成对检查中很有用

进阶思考:

  1. 缺失数字能否用O(n)时间和O(1)空间解决?(提示:数学公式或异或)
  2. 只出现一次的数字能否不用排序,直接用异或解决?
  3. 如果其他元素出现3次,只有一个出现1次,该如何解决?

参考资源

文章标签

#LeetCode #算法 #Java #排序 #数组

喜欢这篇文章吗?别忘了点赞、收藏和分享!你的支持是我创作的最大动力!

相关推荐
小羊没烦恼!3 天前
微服务化的基石——持续集成
java·大数据·word·powerpoint·.net
倒头就睡的小比特3 天前
算法竞赛C++常用的STL
c++·算法
俊昭喜喜里3 天前
java中的继承和多态的区别
java
小羊没烦恼!3 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
譕痕3 天前
JSONObject与JSONArray封装数据格式区别
java·json
胡写代码3 天前
别再前后端各写一套表单校验了
java·后端
小鱼能吃糖3 天前
缺陷修复总览 · mall电商项目:5类缺陷,1个病根,4个业务域
java·电商
此时不提桶,更待何时3 天前
01-06-A-JVM排查实战详解
java·jvm
猎头南楼3 天前
知识社区推荐系统实践:新用户冷启动与长短期兴趣建模的挑战 资深推荐算法工程师
人工智能·深度学习·算法·机器学习
vipxieliang3 天前
ValidX 在 DDD 领域驱动设计中的实践
java·spring boot