1. 题目
26. 删除有序数组中的重复项 - 力扣(LeetCode)
题目描述
给你一个升序排列的数组nums,请你原地删除重复出现的元素,使每个元素只出现一次 ,返回删除后数组的新长度。
元素的相对顺序应当保持一致。
不要使用额外的数组空间,必须在 (O(1)) 额外空间条件下原地修改输入数组。
无需考虑数组中超出新长度后面的元素。
示例
输入:nums = [1,1,2]
输出:2,nums = [1,2,] 输入:nums = [0,0,1,1,1,2,2,3,3,4] 输出:5,nums = [0,1,2,3,4,,,,,]
约束
(1 <= nums.length <= 3 * 10^4)
(-10^4 <= nums[i] <= 10^4)
nums已按升序排列
2. 最佳解题思路描述(快慢双指针,最优)
快慢指针定义:
slow:慢指针,指向当前有效数组最后一位,初始为 0;fast:快指针,遍历全部数组,逐个寻找新的不重复数字;
遍历逻辑:
快指针遇到和
nums[slow]不相等的元素,说明是新唯一值;slow++拓展有效区间,把新值覆盖到nums[slow];最终有效长度为
slow + 1。
优势
时间 (O(n)),仅一次遍历;
空间 (O(1)),无 erase、无数组移位;
有序数组去重通用模板,面试首选。
3. 我的可优化代码(逻辑能 AC,但性能差)
class Solution { public: int removeDuplicates(vector<int>& nums) { int n=nums.size(); int m = n; for(int i=0;i<n-1;i++){ if(nums[i]==nums[i+1]){ nums.erase(nums.begin()+i); n--; i--; m--; } } return m; } };代码说明
逻辑正确性:
发现相邻重复元素就 erase 删除,数组长度 n 同步缩减,i 回退重新判断当前位置;m 记录最终长度,能通过所有用例。
核心缺陷 & 优化点:
vector
erase会让删除点后所有元素整体前移,单次 erase 时间 (O(n)),嵌套循环总时间复杂度 (O(n^2)),数据量大时超时;频繁修改数组长度、回退 i,代码冗余、可读性差;
额外变量 m、n 重复记录长度,无必要。
4. 最优标准代码(快慢双指针)
class Solution { public: int removeDuplicates(vector<int>& nums) { int slow = 0; for (int fast = 1; fast < nums.size(); fast++) { if (nums[fast] != nums[slow]) { slow++; nums[slow] = nums[fast]; } } return slow + 1; } };5. 总结
你的 erase 暴力删除写法可以通过测试,但时间效率极低,面试不推荐;
有序数组原地去重标准解法是快慢双指针,一次遍历无数组删除操作;
核心规律:慢指针保存唯一值末尾,快指针找新值,不同则拓展有效区间;
返回长度切记
slow + 1,slow 是下标不是长度。
6. 相关知识拓展
拓展 1:同类模板联动
LeetCode27 移除元素、26 有序去重、283 移动零共享快慢指针思想:
快指针筛选有效元素,慢指针维护原地结果数组。
拓展 2:vector erase 性能坑
vector 是连续内存,中间删除元素必须移动后方所有元素;
算法题中应尽量避免循环内频繁 erase,改用双指针覆盖赋值替代删除。
拓展 3:拓展变形(保留最多 2 个重复项 LC80)
仅微调判断逻辑,快慢指针框架不变:
int removeDuplicates(vector<int>& nums) { int slow = 1; for(int fast = 2; fast < nums.size(); fast++){ if(nums[fast] != nums[slow-1]){ slow++; nums[slow] = nums[fast]; } } return slow+1; }拓展 4:复杂度对比
erase 暴力写法:时间 (O(n^2)),空间 (O(1));
快慢指针最优解:时间 (O(n)),空间 (O(1))。