leetcode 面试经典 150 题(4/150) 80.删除有序数组中的重复项 II

71 阅读3分钟

题目描述

给定一个有序数组 nums,要求原地删除重复元素,使得每个元素最多出现两次,返回新数组的长度。要求使用 O(1) 额外空间。

解法思路总结

解法一:快慢双指针(固定起点)

核心思想
维护两个指针 slowfast,均从索引 2 开始。slow 表示有效数组的末尾,fast 用于遍历数组。当 nums[fast]nums[slow-2] 不同时,说明当前元素可以保留,将其复制到 slow 位置,并移动 slow

步骤

  1. 初始化 slow = 2, fast = 2
  2. 遍历数组,fast 每次递增。
  3. nums[fast] != nums[slow-2],则将 nums[fast] 复制到 nums[slow],并递增 slow
  4. 遍历结束后,返回 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],则保留该元素。

步骤

  1. 初始化 i = 0
  2. 遍历数组元素 num
    • i < 2num > nums[i-2],则将 num 放入 nums[i],并递增 i
  3. 返回 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
  1. 返回 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),原地修改数组。

选择策略

  • 解法二代码最简洁,适合快速实现。
  • 解法一和解法三逻辑清晰,适合理解双指针的核心思路。