手摸手提桶跑路——LeetCode删除有序数组中的重复元素系列

111 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第15天,点击查看活动详情

26. 删除有序数组中的重复元素

题目描述

给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致

由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。

将最终结果插入 nums 的前 k 个位置后返回 k 。 

不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

示例 1:

输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。
不需要考虑数组中超出新长度后面的元素。

示例 2:

输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。
不需要考虑数组中超出新长度后面的元素。

解题思路

首先抓题干要点。

  • 题目中提到是升序数组,那么相同的元素必然是相邻的。
  • 题目要求原地删除重复出现的元素,使得重复的元素只出现一次,且额外的空间复杂度为 O(1),也就是说我们不能开辟额外的数组空间去将不重复的元素移到新数组中。
  • 元素的相对位置不变意味着在去重后,比如 [1,2,3,3,5],去重后 [1,2,3,5],他们的前后位置关系是保持原样的。

已排序数组要求使用原地算法的话,是相对方便点的。

拿数组 [1,2,3,3,5] 来说,遍历到第二个 3 的时候发现和前面是重复的,那么如果要在原数组上进行修改的话,我们遍历到 5 的时候,应该把 5 挪到第二个 3 的位置,然后继续遍历。那么我们怎么知道第二个 3 的位置呢?

这时候就需要一个指针 slow,来记录当前非重复元素的下标到了哪里。

  1. 初始化 slow=0,因为第一个元素肯定是不重复的。

  2. 初始化 fast = 1,用来进行后续数组的遍历。

  3. 比较 slowfast 是否相同,如果相同,则 fast++,继续遍历。

  4. 否则,更新 slow++,为该非重复元素空出位置,同时 nums[slow] = nums[fast],将新的不重复元素挪到 slow 的位置。

  5. 循环执行 3、4、5操作。

题解

/** 
* @param {number[]} nums 
* @return {number} 
*/
var removeDuplicates = function(nums) {
    let slow = 0, fast = 1;
    const lens = nums.length;
    while(fast < lens) {
        if(nums[slow] == nums[fast]) { // 如果是重复元素则继续遍历
            fast++;
        } else { // 否则将不重复的值填入slow对应的下标,同时让slow的下标+1
            slow++;
            nums[slow] = nums[fast];
        }
    }
    return slow + 1;
};

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

题目描述

给你一个有序数组 nums,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

示例 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 。 
不需要考虑数组中超出新长度后面的元素。

解题思路

这题和上一题差不多,但是允许重复出现两次,也就是说 [1,1,2,2,3] 是被允许的。

那我计数不就好了。每次遍历到的元素都统计出现次数,如果出现的次数小于两次,就将当前遍历到的元素挪到 slow 的位置,如果出现超过两次,那就不挪。

因此我们用一个对象来统计出现次数。

题解

var removeDuplicates = function(nums) {
    let slow = 0, fast = 1;
    const lens = nums.length;
    const dict = {
        [nums[0]]: 1
    }
    while(fast < lens) {
        const k = nums[fast];
        dict[k]++ || (dict[k] = 1);
        if(dict[k] <= 2){
            slow++;
            nums[slow] = k; 
       }
       fast++;
    }
    return slow + 1;
};

嘻嘻,还不错嘛?

好吧,题目要求 O(1) 的额外空间,好家伙你这直接拖家带口了是吧?

我们静下心来仔细来分析下。

数组是连续的对吧?要统计出现的次数对吧?O(1) 的空间复杂度是吧。。?

O(1)  ?一个变量?好像一个变量也行啊,前面统计过的又没用,毕竟数组是连续的,总不能前面的 3 是 3,后面的 3 就不是 3 了吧?

还别说,这就是我的思考过程再现。

var removeDuplicates = function(nums) {
    const lens = nums.length;
    let slow = 0, fast = 1, cnt = 1;
    while(fast < lens) {
        if(nums[fast] === nums[slow]) {
            cnt++;
            if(cnt <= 2) {
                nums[++slow] = nums[fast]; 
           }
        } else {
            cnt = 1;
            nums[++slow] = nums[fast];
        }
        fast++;
    }
    return slow + 1;
};

可以,心满意足.jpg。