题目描述
给定一个有序数组 nums
,要求原地删除重复元素,使得每个元素最多出现两次,返回新数组的长度。要求使用 O(1) 额外空间。
解法思路总结
解法一:快慢双指针(固定起点)
核心思想:
维护两个指针 slow
和 fast
,均从索引 2 开始。slow
表示有效数组的末尾,fast
用于遍历数组。当 nums[fast]
与 nums[slow-2]
不同时,说明当前元素可以保留,将其复制到 slow
位置,并移动 slow
。
步骤:
- 初始化
slow = 2
,fast = 2
。 - 遍历数组,
fast
每次递增。 - 若
nums[fast] != nums[slow-2]
,则将nums[fast]
复制到nums[slow]
,并递增slow
。 - 遍历结束后,返回
slow
作为新长度。
复杂度分析:
- 时间复杂度:O(n)
- 空间复杂度:O(1)
关键点:
- 比较
fast
元素与slow-2
元素,确保最多保留两次重复。 - 适用于数组有序且重复元素连续的场景。
func removeDuplicates(nums []int) int {
n := len(nums)
if n <= 2 {
return n
}
slow, fast := 2, 2
for fast < n {
if nums[slow-2] != nums[fast] {
nums[slow] = nums[fast]
slow++
}
fast++
}
return slow
}
解法二:条件覆盖法
核心思想:
通过单指针 i
记录有效位置。遍历数组时,若当前元素满足 i < 2
或大于 nums[i-2]
,则保留该元素。
步骤:
- 初始化
i = 0
。 - 遍历数组元素
num
:- 若
i < 2
或num > nums[i-2]
,则将num
放入nums[i]
,并递增i
。
- 若
- 返回
i
作为新长度。
复杂度分析:
- 时间复杂度:O(n)
- 空间复杂度:O(1)
关键点:
- 利用数组有序性,直接比较当前元素与
i-2
位置的元素。 - 简洁高效,无需显式维护快慢指针。
func removeDuplicates(nums []int) int {
i := 0
for _, num := range nums {
if i < 2 || num > nums[i-2] {
nums[i] = num
i++
}
}
return i
}
解法三:栈模拟法
核心思想:
模拟栈结构,维护栈大小 stackSize
。从第三个元素开始,若当前元素与栈顶前两个元素不同,则入栈。
步骤:
4. 初始化 stackSize = 2
(前两个元素默认保留)。
5. 从索引 2 开始遍历数组:
- 若
nums[i] != nums[stackSize-2]
,则将当前元素放入栈顶,并递增stackSize
。
- 返回
stackSize
作为新长度。
复杂度分析:
- 时间复杂度:O(n)
- 空间复杂度:O(1)
关键点:
- 栈顶前两个元素决定当前元素是否可保留。
- 类似解法一的思路,但初始条件更明确。
func removeDuplicates(nums []int) int {
stackSize := 2
for i := 2; i < len(nums); i++ {
if nums[i] != nums[stackSize-2] {
nums[stackSize] = nums[i]
stackSize++
}
}
return min(stackSize, len(nums))
}
总结
共同点:
- 均利用双指针或类似思想,通过比较当前元素与前两个有效元素判断是否保留。
- 时间复杂度 O(n),空间复杂度 O(1),原地修改数组。
选择策略:
- 解法二代码最简洁,适合快速实现。
- 解法一和解法三逻辑清晰,适合理解双指针的核心思路。