这道题和上一题 LeetCode 27(移除元素)是亲兄弟,连代码结构都几乎一模一样——都是快慢指针原地修改数组。区别在于:LeetCode 27 是"等于某个值就删",这道题是"和前一个一样就删"。掌握了这个模板,一类题全通了。
题目长什么样
给你一个非严格递增排列的数组 nums,请你原地删除重复出现的元素,使每个元素只出现一次,返回删除后数组的新长度。元素的相对顺序应该保持一致。
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4,_,_,_,_,_]
说人话就是:数组已经排好序了,把重复的挤掉,只留一个,剩下的元素往前挪。返回不重复的元素有几个。
有两个关键条件值得注意:
- “非严格递增”:意味着相等的元素一定挨在一起,不会散落在数组各处。这是能用双指针的前提。
- “保持相对顺序”:说明只能从前往后扫,不能用左右指针交换。
一把就够:快慢指针
这道题只有一种最优解法——快慢指针。因为要保持顺序,左右指针的交换方案直接出局。
思路和 LeetCode 27 完全一致:慢指针 k 标记"已去重区域的末尾",快指针 i 扫描数组。遇到和前一个不一样的元素就写入 k 位置。
唯一的区别是判断条件:
- LeetCode 27:
nums[i] != val(不等于目标值就保留) - LeetCode 26:
nums[i] != nums[i-1](和前一个不一样就保留)
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
if not nums:
return 0
k = 1
for i in range(1, len(nums)):
if nums[i] != nums[i - 1]:
nums[k] = nums[i]
k += 1
return k
注意 k 从 1 开始——因为第一个元素(nums[0])肯定是唯一的,前面没有和它重复的。i 也从 1 开始,因为我们要拿 nums[i] 和 nums[i-1] 比。
跑一遍示例 2 看看:
nums = [0, 0, 1, 1, 1, 2, 2, 3, 3, 4]
↑ 已确定唯一
i=1: nums[1]=0 = nums[0]=0 → 重复,跳过
i=2: nums[2]=1 ≠ nums[1]=0 → 新元素! nums[1]=1, k=2
i=3: nums[3]=1 = nums[2]=1 → 重复,跳过
i=4: nums[4]=1 = nums[3]=1 → 重复,跳过
i=5: nums[5]=2 ≠ nums[4]=1 → 新元素! nums[2]=2, k=3
i=6: nums[6]=2 = nums[5]=2 → 重复,跳过
i=7: nums[7]=3 ≠ nums[6]=2 → 新元素! nums[3]=3, k=4
i=8: nums[8]=3 = nums[7]=3 → 重复,跳过
i=9: nums[9]=4 ≠ nums[8]=3 → 新元素! nums[4]=4, k=5
结果: nums = [0, 1, 2, 3, 4, _, _, _, _, _], k = 5
| 维度 | 值 | 说明 |
|---|---|---|
| 时间 | O(n) | 每个元素只看一次 |
| 空间 | O(1) | 只用了一个变量 k |
和 LeetCode 27 放在一起看
这两道题的代码几乎一样,放在一起对比就清楚了:
LeetCode 27(移除元素) LeetCode 26(删除重复项)
───────────────────── ─────────────────────
k = 0 k = 1
for i in range(len(nums)): for i in range(1, len(nums)):
if nums[i] != val: if nums[i] != nums[i-1]:
nums[k] = nums[i] nums[k] = nums[i]
k += 1 k += 1
return k return k
三处区别:
| 区别 | LeetCode 27 | LeetCode 26 |
|---|---|---|
| 起始值 | k = 0(第一个元素也要判断) | k = 1(第一个元素直接保留) |
| 遍历起点 | i = 0 | i = 1(需要和前一个比较) |
| 判断条件 | nums[i] != val | nums[i] != nums[i-1] |
本质上是同一个模板,只是"保留条件"不同。LeetCode 27 的保留条件是"不等于某个值",这道题的保留条件是"和前一个不一样"。
这道题教会我什么
"有序"是最大的提示
题目说"非严格递增",这意味着相等的元素一定连续排列。如果数组是无序的,nums[i] != nums[i-1] 这个判断就废了——不相邻的重复元素会漏掉。所以看到"有序数组",第一反应就应该是:重复元素一定相邻,可以用双指针。
快慢指针是一个万能模板
k = 起始位置
for i in range(遍历范围):
if 满足保留条件:
nums[k] = nums[i]
k += 1
return k
这个模板能解决所有"原地过滤数组"类的问题,只需要改三个地方:起始值、遍历范围、保留条件。LeetCode 27、LeetCode 26、LeetCode 283(移动零)都是这个套路。
边界条件不能忘
if not nums: return 0 这一行很容易漏掉。当数组为空时,k = 1 会导致越界。LeetCode 27 没有这个问题,因为空数组的 range(len(nums)) 直接不执行循环,k = 0 自然返回。但 LeetCode 26 的 k 从 1 开始,空数组必须单独处理。
完整测试代码
from typing import List
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
if not nums:
return 0
k = 1
for i in range(1, len(nums)):
if nums[i] != nums[i - 1]:
nums[k] = nums[i]
k += 1
return k
if __name__ == "__main__":
s = Solution()
nums = [1, 1, 2]
k = s.removeDuplicates(nums)
print(f"k={k}, nums={nums[:k]}")
nums = [0, 0, 1, 1, 1, 2, 2, 3, 3, 4]
k = s.removeDuplicates(nums)
print(f"k={k}, nums={nums[:k]}")
nums = [1]
k = s.removeDuplicates(nums)
print(f"k={k}, nums={nums[:k]}")
nums = [1, 1, 1, 1]
k = s.removeDuplicates(nums)
print(f"k={k}, nums={nums[:k]}")
nums = [1, 2, 3, 4]
k = s.removeDuplicates(nums)
print(f"k={k}, nums={nums[:k]}")
相关题目推荐:
- LeetCode 27 · 移除元素(同一个模板,保留条件不同)
- LeetCode 80 · 删除有序数组中的重复项 II(进阶版,允许最多出现两次)
- LeetCode 283 · 移动零(同一个模板,把零移到末尾)

960

被折叠的 条评论
为什么被折叠?



