1. 题目解析与核心思路
这道题目的要求是:给定一个有序数组,原地删除重复出现的元素,使得每个元素最多出现两次,返回删除后数组的新长度。必须在原地修改输入数组,并使用O(1)额外空间。
注意:题目中的"原地"意味着不能使用额外的数组空间,必须在原数组上直接修改。这是算法面试中常见的空间复杂度要求。
1.1 题目示例分析
以示例 nums = [1,1,1,2,2,3] 为例:
- 第一个1出现第三次时需要删除
- 第一个2出现第二次时可以保留
- 3只出现一次可以保留
最终结果应为[1,1,2,2,3],长度为5
1.2 双指针解法原理
双指针法是解决这类数组原地修改问题的经典方法:
- 慢指针(slow):指向当前可以放置新元素的位置
- 快指针(fast):遍历数组寻找下一个需要保留的元素
关键点在于如何判断一个元素是否已经出现了两次以上。我们可以比较 nums[fast] 和 nums[slow-2]:
- 如果相等,说明当前元素已经出现了两次,应该跳过
- 如果不相等,说明可以保留当前元素
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 详细实现步骤
2.1 基础情况处理
首先处理数组长度小于等于2的情况,这些情况直接返回原数组即可:
python复制if len(nums) <= 2:
return len(nums)
2.2 双指针初始化
设置两个指针,初始位置如下:
- slow = 2(因为前两个元素无论如何都可以保留)
- fast = 2(从第三个元素开始检查)
python复制slow = 2
for fast in range(2, len(nums)):
2.3 核心比较逻辑
在循环中,我们比较当前元素与前两个已保留元素:
python复制if nums[fast] != nums[slow-2]:
nums[slow] = nums[fast]
slow += 1
这个判断条件的含义是:只有当当前元素与slow指针前两个位置的元素不同时,才保留当前元素。
2.4 完整代码实现
python复制def removeDuplicates(nums):
if len(nums) <= 2:
return len(nums)
slow = 2
for fast in range(2, len(nums)):
if nums[fast] != nums[slow-2]:
nums[slow] = nums[fast]
slow += 1
return slow
3. 复杂度分析与边界条件
3.1 时间复杂度
- 我们只遍历了一次数组,所以时间复杂度是O(n)
- 每个元素最多被比较和赋值一次
3.2 空间复杂度
- 只使用了常数个额外变量(slow和fast)
- 满足题目要求的O(1)空间复杂度
3.3 边界条件测试
需要特别注意以下几种边界情况:
- 空数组:
[]→ 返回0 - 单元素数组:
[1]→ 返回1 - 两元素数组:
[1,1]→ 返回2 - 所有元素相同:
[1,1,1,1,1]→ 应返回[1,1] - 无需删除任何元素的情况:
[1,2,3,4,5]→ 应返回原数组
4. 算法优化与变种
4.1 通用化解法
这个问题可以推广到"允许每个元素最多出现k次"的情况。只需要将代码中的2改为k即可:
python复制def removeDuplicatesK(nums, k):
if len(nums) <= k:
return len(nums)
slow = k
for fast in range(k, len(nums)):
if nums[fast] != nums[slow-k]:
nums[slow] = nums[fast]
slow += 1
return slow
4.2 其他类似题目
这种双指针方法可以解决一系列数组去重问题:
- LeetCode 26. 删除有序数组中的重复项(每个元素只保留一次)
- LeetCode 27. 移除元素(删除所有等于某个值的元素)
- LeetCode 283. 移动零(将所有0移动到数组末尾)
5. 实际应用场景
这种数组去重算法在实际开发中有多种应用:
- 数据处理流水线:在数据预处理阶段,经常需要清理重复数据,同时保留一定数量的样本
- 日志分析系统:处理重复的日志条目时,可能需要保留少量样本用于调试
- 数据库优化:某些数据库引擎在合并数据块时会使用类似算法减少存储空间
- 图像处理:处理像素序列时可能需要类似的去重操作
6. 常见错误与调试技巧
6.1 典型错误模式
- 指针越界:没有正确处理数组长度小于2的情况,导致访问nums[slow-2]越界
- 比较逻辑错误:错误地比较nums[fast]和nums[fast-2],这会漏掉一些该保留的元素
- 返回值错误:返回了len(nums)而不是slow,导致结果不正确
6.2 调试建议
- 使用小规模测试用例手动模拟指针移动
- 打印每次迭代后的数组状态:
python复制print(f"fast={fast}, slow={slow}, nums={nums}") - 特别注意数组前几个元素的处理是否正确
7. 不同语言实现对比
7.1 Java实现
java复制public int removeDuplicates(int[] nums) {
if (nums.length <= 2) return nums.length;
int slow = 2;
for (int fast = 2; fast < nums.length; fast++) {
if (nums[fast] != nums[slow-2]) {
nums[slow++] = nums[fast];
}
}
return slow;
}
7.2 C++实现
cpp复制int removeDuplicates(vector<int>& nums) {
if (nums.size() <= 2) return nums.size();
int slow = 2;
for (int fast = 2; fast < nums.size(); fast++) {
if (nums[fast] != nums[slow-2]) {
nums[slow++] = nums[fast];
}
}
return slow;
}
7.3 JavaScript实现
javascript复制function removeDuplicates(nums) {
if (nums.length <= 2) return nums.length;
let slow = 2;
for (let fast = 2; fast < nums.length; fast++) {
if (nums[fast] !== nums[slow-2]) {
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
8. 算法思维训练建议
要掌握这类双指针问题,建议:
- 多画图:在纸上画出指针移动的过程,直观理解算法
- 从简单开始:先解决每个元素只出现一次的问题(LeetCode 26),再扩展到本题
- 总结模式:注意这类问题的共同特点(有序数组、原地修改、双指针)
- 变种练习:尝试修改条件(如允许重复k次、基于其他条件删除元素等)
在实际面试中,这类题目常常作为热身题出现。掌握它们不仅能帮助解决具体问题,更能培养处理数组类问题的通用思维模式。
