删除有序数组中的重复项 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 个元素即为处理后的数组。

代码解释

typescript 复制代码
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]

测试代码

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

typescript 复制代码
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)。

相关推荐
爱编程— 的小李9 分钟前
有序序列合并(c语言)
c语言·算法
云卓SKYDROID11 分钟前
无人机反步滑膜控制算法!
算法·无人机·知识科普·云卓科技·反步滑膜控制算法
云卓科技11 分钟前
无人机之自动控制原理篇
科技·算法·目标检测·机器人·无人机
云卓科技13 分钟前
无人机之集群控制方法篇
科技·算法·无人机·交互·制造
混迹网络的权某13 分钟前
每天一道C语言精选编程题之求数字的每⼀位之和
c语言·开发语言·考研·算法·改行学it·1024程序员节
Curry_Math17 分钟前
LeetCode 热题 100之链表3
算法·leetcode·链表
hn小菜鸡3 小时前
LeetCode 2058.找出临界点之间的最小和最大距离
算法·leetcode·职场和发展
liuyang-neu3 小时前
力扣 简单 70.爬楼梯
java·算法·leetcode
IronmanJay3 小时前
【LeetCode每日一题】——862.和至少为 K 的最短子数组
数据结构·算法·leetcode·前缀和·双端队列·1024程序员节·和至少为 k 的最短子数组
OT.Ter3 小时前
【力扣打卡系列】二分查找(搜索旋转排序数组)
算法·leetcode·职场和发展·go·二分查找