删除有序数组中的重复项 II 图解题解
这道题到底在问什么
- 输入
- nums = [0,0,1,1,1,1,2,3,3]
- 输出
- k = 7,前 7 个变为 [0,0,1,1,2,3,3]
最优解:一步一步想明白
- 3核心就一句话:当前数和「已写区倒数第二个」相等,才说明已经有两个了、要丢;否则保留。下面每一帧都在套这条判断。
- 4开始前:写指针 k=0,读指针 i=0。绿色 i 是正在看的数,蓝色 k 是接下来要写的位置。
- 5读指针 i 走到下标 0,看到 0。写指针 k=0 还小于 2,前两个位置无条件保留,要把 nums[0]=0 写下来。
- 6保留:把 0 写到第 0 位(蓝色写指针处),写指针 k 前进到 1。绿色高亮的就是已经定稿的结果区。
- 7读指针 i 走到下标 1,看到 0。写指针 k=1 还小于 2,前两个位置无条件保留,要把 nums[1]=0 写下来。
- 8保留:把 0 写到第 1 位(蓝色写指针处),写指针 k 前进到 2。绿色高亮的就是已经定稿的结果区。
- 9读指针 i 走到下标 2,看到 1。当前数 1 和已写区倒数第二个 nums[0]=0 不相等,说明 1 还没攒够两个,保留。
- 10保留:把 1 写到第 2 位(蓝色写指针处),写指针 k 前进到 3。绿色高亮的就是已经定稿的结果区。
- 11读指针 i 走到下标 3,看到 1。当前数 1 和已写区倒数第二个 nums[1]=0 不相等,说明 1 还没攒够两个,保留。
- 12保留:把 1 写到第 3 位(蓝色写指针处),写指针 k 前进到 4。绿色高亮的就是已经定稿的结果区。
- 13读指针 i 走到下标 4,看到 1。当前数 1 和已写区倒数第二个 nums[2]=1 相等,说明 1 已经有两个了,这第三个跳过不写。
- 14跳过:这第三个 1 是多余的(标红),不写进结果区,写指针 k 留在原地不动。
- 15读指针 i 走到下标 5,看到 1。当前数 1 和已写区倒数第二个 nums[2]=1 相等,说明 1 已经有两个了,这第三个跳过不写。
- 16跳过:这第三个 1 是多余的(标红),不写进结果区,写指针 k 留在原地不动。
- 17读指针 i 走到下标 6,看到 2。当前数 2 和已写区倒数第二个 nums[2]=1 不相等,说明 2 还没攒够两个,保留。
- 18保留:把 2 写到第 4 位(蓝色写指针处),写指针 k 前进到 5。绿色高亮的就是已经定稿的结果区。
- 19读指针 i 走到下标 7,看到 3。当前数 3 和已写区倒数第二个 nums[3]=1 不相等,说明 3 还没攒够两个,保留。
- 20保留:把 3 写到第 5 位(蓝色写指针处),写指针 k 前进到 6。绿色高亮的就是已经定稿的结果区。
- 21读指针 i 走到下标 8,看到 3。当前数 3 和已写区倒数第二个 nums[4]=2 不相等,说明 3 还没攒够两个,保留。
- 22保留:把 3 写到第 6 位(蓝色写指针处),写指针 k 前进到 7。绿色高亮的就是已经定稿的结果区。
- 23扫完整趟,写指针停在 7。数组前 7 个(绿色)就是答案 [0,0,1,1,2,3,3],每个数最多两个。
⚠️ 容易写错的地方
✗ 错:和 nums[k-1] 比较
✓ 对:和 nums[k-2] 比较
要允许每个数留两个,得看「倒数第二个」是否已等于当前数;比 k-1 只能留一个,变成了去重 I
✗ 错:忘了 k<2 的兜底,直接访问 nums[k-2]
✓ 对:先判 k<2 无条件保留
k 为 0 或 1 时 nums[k-2] 会越界(负下标),前两个位置必须先无条件写入
✗ 错:先删元素再移动,O(n²) 搬数据
✓ 对:用写指针原地覆盖
真删除会反复搬移后续元素;写指针只在该写时覆盖,一趟 O(n) 搞定
完整代码(Python / C++ / Java)
Python
def removeDuplicates(nums):
k = 0 # 写指针:下一个该写的位置
for x in nums: # x 是当前读到的数
if k < 2 or nums[k-2] != x: # 没满两个 → 保留
nums[k] = x
k += 1
return k # 新长度C++
int removeDuplicates(vector<int>& nums){
int k = 0;
for (int x : nums) {
if (k < 2 || nums[k-2] != x) {
nums[k] = x;
k++;
}
}
return k;
}Java
public int removeDuplicates(int[] nums) {
int k = 0;
for (int x : nums) {
if (k < 2 || nums[k-2] != x) {
nums[k] = x;
k++;
}
}
return k;
}复杂度
时间
O(n)
读指针 i 把数组从头到尾扫一遍,每个数只看一次
空间
O(1)
只用一个写指针 k,在原数组上覆盖,不开额外数组
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 删除有序数组中的重复项 II 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
写指针 k 代表什么?+
k 是「结果区下一个该写的位置」,同时也是当前已确定保留的元素个数。扫描结束时,k 就是新长度,nums 前 k 个就是答案。
如果题目改成「每个元素最多保留一次」(去重 I)怎么改?+
把判断里的 nums[k-2] 换成 nums[k-1]、k<2 换成 k<1(或 k==0)即可。限额从 2 变 1,比较的「倒数第几个」也跟着变。
为什么可以直接在原数组上覆盖、不怕把还没读的数冲掉?+
写指针 k 永远不超过读指针 i(k≤i),要写的位置总在已经读过的区域里,不会覆盖到后面还没处理的数。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 删除有序数组中的重复项 II 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。