删除有序数组中的重复项 II

删除有序数组中的重复项 II

问题描述

给定一个有序数组 nums,要求你原地删除重复出现的元素,使得每个元素最多出现两次,并返回删除后数组的新长度 k。要求满足以下条件:

  • 原地修改数组:不能使用额外的数组空间,必须在原数组上进行修改。
  • 保持元素的相对顺序一致:删除多余的重复元素后,剩下的元素的顺序必须与原数组中元素的顺序一致。
  • 返回值:新的数组长度 k,其中前 k 个元素为处理后的数组元素。
示例

示例 1:

输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
解释:函数应返回新的长度 length = 5,数组的前五个元素被修改为 1, 1, 2, 2, 3。

示例 2:

输入:nums = [0,0,1,1,1,1,2,3,3]
输出:7, nums = [0,0,1,1,2,3,3]
解释:函数应返回新的长度 length = 7,数组的前七个元素被修改为 0, 0, 1, 1, 2, 3, 3。

解题思路

由于数组是有序的,重复的元素会连续出现。我们需要允许每个元素最多出现两次。因此,可以使用双指针的方法遍历数组,控制元素的出现次数。

  • 慢指针(k):指向数组中下一个要放置元素的位置,也即新数组的长度。
  • 快指针(i):用于遍历整个数组。

算法步骤:

  1. 初始化指针:

    • k = 0,用于记录新数组的长度。
  2. 遍历数组:

    • 对于每个元素 nums[i],执行以下操作:
      • 如果 k < 2,说明前面少于两个元素,可以直接复制当前元素。
      • 或者 nums[i] != nums[k - 2],表示当前元素不等于新数组中倒数第二个元素,说明当前元素出现次数未超过两次,可以复制。
      • 否则,跳过当前元素。
  3. 复制元素:

    • 将符合条件的 nums[i] 复制到 nums[k]
    • 增加 k 的值。
  4. 返回结果:

    • 遍历结束后,k 即为新数组的长度。
    • 数组 nums 的前 k 个元素即为处理后的数组。

代码解释

function removeDuplicates(nums: number[]): number {
    let k = 0; // 初始化新数组的长度

    for (let i = 0; i < nums.length; i++) {
        if (k < 2 || nums[i] !== nums[k - 2]) {
            nums[k] = nums[i]; // 将当前元素放到新数组中
            k++; // 增加新数组的长度
        }
        // 如果当前元素等于 nums[k - 2],则跳过,不复制
    }

    return k; // 返回新数组的长度
}

详细解释:

  • 变量初始化:

    • let k = 0;:初始化新数组的长度为 0。
  • 遍历数组:

    • for (let i = 0; i < nums.length; i++) { ... }:遍历数组中的每个元素。
  • 条件判断:

    • if (k < 2 || nums[i] !== nums[k - 2]) { ... }

      • k < 2:前面少于两个元素,可以直接复制。
      • nums[i] !== nums[k - 2]:当前元素不等于新数组中倒数第二个元素,说明未出现超过两次。
  • 复制元素:

    • nums[k] = nums[i];:将符合条件的元素复制到位置 k

    • k++;:增加新数组的长度。

  • 跳过元素:

    • 如果上述条件不满足,说明当前元素已经出现超过两次,直接跳过。
  • 返回结果:

    • return k;:返回新数组的长度。
示例演示

以示例 1 为例,nums = [1,1,1,2,2,3]

  • 初始状态:

    • nums = [1,1,1,2,2,3]
    • k = 0
  • 遍历过程:

    1. i = 0

      • k = 0 < 2,条件满足。
      • nums[k] = nums[0];nums[0] = 1
      • k = 1
    2. i = 1

      • k = 1 < 2,条件满足。
      • nums[k] = nums[1];nums[1] = 1
      • k = 2
    3. i = 2

      • k = 2 >= 2nums[2] = 1nums[k - 2] = nums[0] = 1
      • nums[2] === nums[k - 2],条件不满足,跳过。
    4. i = 3

      • nums[3] = 2nums[k - 2] = nums[0] = 1
      • nums[3] !== nums[k - 2],条件满足。
      • nums[k] = nums[3];nums[2] = 2
      • k = 3
    5. i = 4

      • nums[4] = 2nums[k - 2] = nums[1] = 1
      • nums[4] !== nums[k - 2],条件满足。
      • nums[k] = nums[4];nums[3] = 2
      • k = 4
    6. i = 5

      • nums[5] = 3nums[k - 2] = nums[2] = 2
      • nums[5] !== nums[k - 2],条件满足。
      • nums[k] = nums[5];nums[4] = 3
      • k = 5
  • 结果:

    • 返回 k = 5
    • nums 的前 5 个元素为 [1,1,2,2,3]
时间和空间复杂度分析
  • 时间复杂度:O(n),其中 n 是数组 nums 的长度。需要遍历一次数组。

  • 空间复杂度:O(1),只使用了常数级别的额外空间。

总结
  • 利用数组有序的特点:因为数组是有序的,相同的元素必然连续出现。

  • 双指针法:使用慢指针 k 控制新数组的长度,快指针 i 遍历原数组。

  • 控制元素出现次数:通过检查 nums[i] 是否等于 nums[k - 2],来判断当前元素是否已经出现两次。

  • 原地修改数组:在满足条件的情况下,将元素复制到位置 k,并增加 k 的值。

注意事项
  • 边界条件:当 k < 2 时,无需检查,直接复制元素。

  • 元素比较:始终将当前元素与新数组中倒数第二个元素进行比较,以控制出现次数不超过两次。

扩展

如果题目要求每个元素最多出现 k 次,该如何修改算法?

  • 将条件 k < 2 改为 k < n,并将比较条件改为 nums[i] !== nums[k - n]

测试代码

以下是测试代码,用于验证函数的正确性:

function testRemoveDuplicates() {
    const nums1 = [1,1,1,2,2,3];
    const expectedNums1 = [1,1,2,2,3];

    const k1 = removeDuplicates(nums1);

    console.assert(k1 === expectedNums1.length, `测试 1 失败:期望长度 ${expectedNums1.length},实际长度 ${k1}`);

    for (let i = 0; i < k1; i++) {
        console.assert(nums1[i] === expectedNums1[i], `测试 1 失败:索引 ${i},期望值 ${expectedNums1[i]},实际值 ${nums1[i]}`);
    }

    console.log("测试 1 通过!");

    const nums2 = [0,0,1,1,1,1,2,3,3];
    const expectedNums2 = [0,0,1,1,2,3,3];

    const k2 = removeDuplicates(nums2);

    console.assert(k2 === expectedNums2.length, `测试 2 失败:期望长度 ${expectedNums2.length},实际长度 ${k2}`);

    for (let i = 0; i < k2; i++) {
        console.assert(nums2[i] === expectedNums2[i], `测试 2 失败:索引 ${i},期望值 ${expectedNums2[i]},实际值 ${nums2[i]}`);
    }

    console.log("测试 2 通过!");
}

testRemoveDuplicates();

结论

该方法利用了数组有序的特性,使用双指针高效地完成了任务,时间复杂度为 O(n),空间复杂度为 O(1)。

### 删除有序数组中的重复项 对于删除有序数组中的重复项,可以采用双指针方法来优化时间效率和空间复杂度。以下是基于 Python 的实现: #### 方法描述 通过两个指针 `slow` 和 `fast` 遍历数组。其中,`slow` 指向当前有效部分的最后一个位置,而 `fast` 负责遍历整个数组。当发现新的不同元素时,将其放置到 `slow` 所指向的位置,并移动 `slow`。 这种方法的时间复杂度为 \(O(n)\),空间复杂度为 \(O(1)\)[^3]。 #### 实现代码 ```python class Solution: def removeDuplicates(self, nums: list[int]) -> int: if not nums: return 0 n = len(nums) slow = 1 for fast in range(1, n): if nums[fast] != nums[fast - 1]: nums[slow] = nums[fast] slow += 1 return slow ``` 上述代码实现了从有序数组中移除重复项的功能。最终返回的是去重后的数组长度,同时原数组被修改以保留唯一值的部分。 --- 如果数组不是有序的,则可以通过哈希表的方式解决此问题。具体做法如下: #### 哈希表方法描述 利用字典或集合存储已经遇到过的元素,从而判断某个元素是否重复。该方法能够保证原始顺序不变,但需要额外的空间开销\(O(n)\)[^1]。 #### 实现代码 ```python def remove_duplicates_unsorted(nums: list[int]): seen = set() result = [] for num in nums: if num not in seen: result.append(num) seen.add(num) return result ``` 以上函数适用于无序数组的情况,它会返回一个新的不含重复项的列表。 --- ### 总结 - 对于 **有序数组**,推荐使用双指针技术,在线性时间内完成操作且无需额外内存。 - 如果面对的是 **无序数组** 或无法假设输入已排序的情况下,则应考虑借助辅助数据结构如哈希集合作业。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小李学软件

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值