1. 题目
80. 删除有序数组中的重复项 II - 力扣(LeetCode)
题目描述
给你一个有序数组 nums ,请你原地 删除重复出现的元素,使每个元素 最多出现两次 ,返回删除后数组的新长度。
不要使用额外的数组空间,必须在 (O(1)) 额外空间并原地修改输入数组。
元素的相对顺序应该保持一致。无需考虑数组超出新长度后的部分。
示例
输入:nums = 1,1,1,2,2,3
输出:5,nums = 1,1,2,2,3,* 输入:nums = 0,0,1,1,1,1,2,3,3 输出:7,nums = 0,0,1,1,2,3,3,*,_
约束
-
(1 <= nums.length <= 3*10^4)
-
(-10^4 <= numsi <= 10^4)
-
nums已按升序排列
2. 最佳解题思路描述(快慢指针通用模板,本题最优)
-
核心规则:有序数组允许同一个数字最多保留 2 个;
-
快慢指针定义:
-
r(慢指针):下一个可填充的有效位置,初始为 2(前两个数字天然合法,直接保留); -
l(快指针):遍历全部数组,从下标 2 开始向后扫描;
-
-
判断逻辑:
对比当前快指针值
nums[l]和有效区间倒数第二个值nums[r-2]:-
不相等:说明当前数字出现次数≤2,是合法元素,覆盖到
nums[r],慢指针右移; -
相等:说明当前数字已经存在两个,直接跳过;
-
-
遍历结束后
r即为有效数组长度,直接返回。
优势
-
时间复杂度 (O(n)),仅单次遍历;
-
空间复杂度 (O(1)),原地赋值,无 erase、无额外容器;
-
可拓展通用:最多保留 k 个重复项,只需把初始 r、对比下标改成 k。
3. 我的可优化代码(逻辑完全正确,是标准最优解,仅微小细节优化)
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int n=nums.size();
if(n<2){
return n;
}
int r = 2;
for(int l = r;l<n;l++){
if(nums[l]!=nums[r-2]){
nums[r++]=nums[l];
}
}
return r;
}
};
代码说明
-
正确性:逻辑无任何 bug,能通过全部测试用例,是行业标准最优写法;
-
可优化小细节:
-
边界判断
if(n<2)可以省略:数组长度 0/1 时,for 循环不会执行,直接 return r=2 会出错,因此该判断必须保留; -
变量名语义优化:
r改名为slow、l改名为fast,和 26、27 题命名统一,方便记忆; -
循环简写:
nums[slow++] = nums[fast]压缩一行。
-
4. 标准规范版代码(统一快慢指针命名)
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int n = nums.size();
if (n <= 2) return n;
int slow = 2;
for (int fast = 2; fast < n; fast++) {
if (nums[fast] != nums[slow - 2]) {
nums[slow++] = nums[fast];
}
}
return slow;
}
};
5. 总结
-
你的代码思路、实现完全正确,是本题最优解法,面试可直接书写;
-
核心判断记忆点:最多保留 2 个,就和
slow-2对比; -
初始慢指针从 2 开始:前两个数字一定合法,无需校验;
-
有序数组限制是该解法成立的前提,无序数组不能使用此模板;
-
无 erase、无嵌套循环,性能远优于暴力删除方案。
6. 相关知识拓展
拓展 1:通用模板 ------ 最多保留 k 个重复元素
将本题逻辑泛化,任意 k 都适用:
int removeDuplicates(vector<int>& nums, int k) {
int n = nums.size();
if (n <= k) return n;
int slow = k;
for (int fast = k; fast < n; fast++) {
if (nums[fast] != nums[slow - k]) {
nums[slow++] = nums[fast];
}
}
return slow;
}
-
LC26(最多保留 1 个):k=1,slow 初始 = 1,对比 slow-1;
-
LC80(最多保留 2 个):k=2,slow 初始 = 2,对比 slow-2。
拓展 2:暴力 erase 对比(不推荐)
循环 erase 删除第三个及以后重复数字,时间复杂度 (O(n^2)),大数据超时:
int removeDuplicates(vector<int>& nums) {
for(int i=2;i<nums.size();i++){
if(nums[i]==nums[i-2]){
nums.erase(nums.begin()+i);
i--;
}
}
return nums.size();
}
拓展 3:同类快慢指针题目汇总
-
LC27 移除指定元素;
-
LC26 有序数组去重(最多 1 个);
-
LC80 有序数组去重(最多 2 个);
-
LC283 移动零;
全部遵循:fast 遍历筛选,slow 维护有效数组。
拓展 4:复杂度对比
-
快慢指针最优解:(O(n)) 时间,(O(1)) 空间;
-
erase 暴力删除:(O(n^2)) 时间,(O(1)) 空间。