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))
-
- [2.1 题目描述](#2.1 题目描述)
- [2.2 解题思路](#2.2 解题思路)
- [2.3 代码实现](#2.3 代码实现)
- [2.4 代码逐行解释](#2.4 代码逐行解释)
- [2.5 执行流程详解](#2.5 执行流程详解)
- [2.6 算法图解](#2.6 算法图解)
- [2.7 复杂度分析](#2.7 复杂度分析)
- [2.8 边界情况](#2.8 边界情况)
- [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 解题思路
这道题的核心思想是:将数组排序后,逐个检查每个位置的值是否等于其索引。
解题步骤:
- 对数组进行排序
- 从0开始遍历,检查每个位置
- 如果当前值不等于索引,说明这个索引就是缺失的数字
- 如果遍历完都没找到,说明缺失的是最后一个数
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 解题思路
通过排序后,相同的元素会相邻。利用这一特性,可以逐对检查元素:
解题思路:
- 对数组排序
- 从索引0开始,每次检查两个元素(i和i+1)
- 如果nums[i] != nums[i+1],说明nums[i]是只出现一次的元素
- 如果遍历完偶数索引都没找到,说明最后一个元素是答案
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 排序在查找中的应用
排序的作用:
- 使元素有序,便于逐个比较
- 使相同元素相邻,便于成对检查
- 简化查找逻辑,降低算法复杂度
排序方法:
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. 总结
今天我们学习了两道查找类题目:
- 缺失数字:通过排序后逐个比较,找到与索引不匹配的数字
- 只出现一次的数字:通过排序后成对检查,找到不成对的元素
核心收获:
- 排序是解决查找问题的重要工具
- 排序后的数组具有很多有用的性质
- 利用索引与值的关系可以简化问题
- 奇偶索引在成对检查中很有用
进阶思考:
- 缺失数字能否用O(n)时间和O(1)空间解决?(提示:数学公式或异或)
- 只出现一次的数字能否不用排序,直接用异或解决?
- 如果其他元素出现3次,只有一个出现1次,该如何解决?
参考资源
文章标签
#LeetCode #算法 #Java #排序 #数组
喜欢这篇文章吗?别忘了点赞、收藏和分享!你的支持是我创作的最大动力!