删除有序数组中的重复项 图解题解
升序数组原地去重:slow 盯住已确认区末位,fast 找到新数就填进来——一趟扫、O(1) 空间。
slow 指向「已确认区的末位」,fast 往右扫。fast 遇到和 slow 相同的值就直接跳过;遇到不同的值(新数字),就让 slow 先往右走一格,再把新数字**写到** slow 的位置——是写入覆盖,不是删除。slow 左边(含 slow)始终是去重后的干净结果,fast 扫完一趟结束。
这道题到底在问什么
- nums
- [1, 1, 2, 2, 3]
- 输出
- 3,且前 3 位变成 [1, 2, 3]
最优解:一步一步想明白
- 3最直白的做法:发现重复就删掉它,后面的元素整体往前挪一格。每删一次都要搬一长串,最坏要搬 n 的平方次,太慢。
- 4换个思路:慢指针 slow 指向「最后一个已确定的不重复数」;快指针 fast 一路向前。fast 撞到和 slow 不一样的新数,就 slow 前进一格、把新数写过去。slow 左边永远是去好重的结果。
- 5slow=0,fast 从 1 出发紫色 slow 停在 0 号位——第一个数没有可比的前一个,天然保留。蓝色 fast 从 1 号位出发去找新数。
- 6nums[1]=1 与 nums[slow]=1 相同fast 指的 1,和 slow 指的 1 一样,是重复数。slow 不动,只让 fast 往前走。
- 7nums[2]=2 与 nums[slow]=1 不同fast 指的 2,和 slow 指的 1 不一样,是个新数字!该把它码到 slow 后面了。
- 8slow 前进到 1写之前先让 slow 前进一格,到 1 号位。这个格子原来放着多余的 1,正好拿来安放新数。
- 9nums[1] 被覆盖成 2把 2 写进 1 号位,覆盖掉原来多余的 1。数组前段变成 [1, 2],去重结果又长了一位。
- 10nums[3]=2 与 nums[slow]=2 相同fast 指的 2,和 slow 指的 2 相同,又是重复。slow 不动,fast 继续往前。
- 11nums[4]=3 与 nums[slow]=2 不同fast 走到最后,指的 3,和 slow 指的 2 不一样,又是新数字。
- 12slow 前进到 2老规矩,先让 slow 前进到 2 号位,腾出新数该放的格子。
- 13nums[2] 被覆盖成 3把 3 写进 2 号位。前段变成 [1, 2, 3],三个不重复的数都归位了。
- 14去重长度 = slow + 1 = 3fast 走完了。slow 停在 2 号位,前 slow+1 也就是 3 个数 [1, 2, 3] 就是去重结果,返回 3。后面的旧数据不用管。
- 15新数:先进位 → 再覆盖把核心动作再慢放一遍:fast 找到新数 3,slow 先进一格到 2,再把 3 覆盖写进去。重复数就只让 fast 走、slow 原地不动。记住这两拍。
- 18slow 是写指针、fast 是读指针。读到和 slow 不同的新数,才让 slow 进位并写入。删除有序数组重复项、移除元素、移动零,全是这套快慢指针。
- 20slow 全程不动 → 返回 1试个全是 2 的数组:fast 每次比都相同,slow 一直停在 0。返回 slow+1 = 1,正好——去重后只剩一个 2。先进位的写法在这里也不会误覆盖。
- 24练熟这题,再去同类题迁移:LC27 改「保留条件」、LC80 改「比较对象」、LC283 改成交换。结构都一样,遇到卡点可以问问 AI 助教小欧,或到通关训练里实战一遍。
⚠️ 容易写错的地方
✗ 错:fast 撞到新数就直接 nums[slow]=nums[fast],忘了先 slow+=1
✓ 对:先 slow += 1,再 nums[slow] = nums[fast]
不先进位就会把新数写回 slow 自己那格,等于原地踏步、覆盖了已确认的数
✗ 错:fast 拿 nums[fast] 去和 nums[fast-1] 比
✓ 对:和 nums[slow] 比
slow 指的才是「最后一个确定保留的数」;和 fast-1 比在连续重复段里会判错
✗ 错:返回 slow 或 slow-1
✓ 对:返回 slow + 1
slow 是最后一个有效元素的下标,下标从 0 起,所以个数是 slow + 1
完整代码(Python / C++ / Java)
Python
class Solution:
def removeDuplicates(self, nums):
if not nums:
return 0
slow = 0 # 去重边界
for fast in range(1, len(nums)): # 快指针探路
if nums[fast] != nums[slow]: # 遇到新数
slow += 1 # 先进位
nums[slow] = nums[fast] # 再覆盖写入
return slow + 1 # 长度 = slow+1C++
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if (nums.empty()) return 0;
int slow = 0;
for (int fast = 1; fast < (int)nums.size(); fast++) {
if (nums[fast] != nums[slow]) {
slow++;
nums[slow] = nums[fast];
}
}
return slow + 1;
}
};Java
class Solution {
public int removeDuplicates(int[] nums) {
if (nums.length == 0) return 0;
int slow = 0;
for (int fast = 1; fast < nums.length; fast++) {
if (nums[fast] != nums[slow]) {
slow++;
nums[slow] = nums[fast];
}
}
return slow + 1;
}
}套路模板 · 快慢指针「读写分离」(背骨架)
# 原地保留「该留的元素」,去重/移除都套这个骨架
slow = 0 # 写指针:已确定边界
for fast in range(1, len(nums)): # 读指针:探路
if 是要保留的新元素(nums[fast]): # 改这一行的判据
slow += 1
nums[slow] = nums[fast]
return slow + 1 # 结果长度int slow = 0;
for (int fast = 1; fast < (int)nums.size(); fast++) {
if (/* 是要保留的新元素 */) {
slow++;
nums[slow] = nums[fast];
}
}
return slow + 1;int slow = 0;
for (int fast = 1; fast < nums.length; fast++) {
if (/* 是要保留的新元素 */) {
slow++;
nums[slow] = nums[fast];
}
}
return slow + 1;复杂度
时间复杂度
O(n)
fast 从头到尾只走一遍,slow 只增不减,总共 n 步
空间复杂度
O(1)
全程在原数组上覆盖,不额外开空间
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 删除有序数组中的重复项 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这题为什么不能用哈希表去重?+
可以但没必要。数组已升序,重复必相邻,双指针就能 O(1) 空间原地解决;哈希表反而多花 O(n) 空间,还破坏了原地的要求。
如果允许每个数最多保留两个(LC80)怎么改?+
把比较对象从 nums[slow] 换成 nums[slow-1]:只要 fast 的数和倒数第二个保留的不同就写入。slow 从 2 起步,骨架完全一样。
数组无序还能这么做吗?+
不能。本解法的正确性建立在「重复元素相邻」上。无序就得先排序 O(n log n),或改用哈希集合。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。