LeetCode每日一题,删除有序数组中的重复项 II | 刷题打卡

201 阅读3分钟

题目

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

leetcode-cn.com/problems/re…

公众号 《java编程手记》记录JAVA学习日常,分享学习路上点点滴滴,从入门到放弃,欢迎关注

java编程手记

描述

难度:中等

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 最多出现两次 ,返回删除后数组的新长度。

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

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

示例 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 <= nums.length <= 3 * 104
-104 <= nums[i] <= 104
nums 已按升序排列

Solution

双指针缓存解法

解题思路

没有看错,跟昨天的题几乎差不多,最大的变化是难度由简单变为中等,删除重复项最多出现一次,变成最多出现两次,可以先复习下昨天的文章看下上次的解法,在原有解法的基础上,看到这个进化题目第一反应就是加缓存,判断是否超过两次,以空间换时间,

  • 使用Map进行缓存元素及对应出现的次数
  • 设定快慢指针
    • 慢指针slow指向新组成的数组的最新位置
    • 快指针fast指向数组遍历的最新位置
  • 依次遍历老数组,每次遍历元素fast指向的元素和slow指向的新数组元素做比较
    • nums[i] == nums[a] ,元素相等
      • map.get(nums[fast]) 拿出对应元素出现的次数,==null || ==1 则更新出现缓存值为2
        • ==null的case为 slow坐标为0fast1,这时候会出现相等但是取出的缓存值为null的情况,直接更新为2即可
    • nums[i] != nums[a],元素不相等,
      • a指针后移,后移后赋值新的nums[a]
      • 更新元素出现次数缓存为1
      • 指针继续遍历
    • 遍历结束
  • 返回结果a+1,因为a0开始算起,最终计算长度需要+1

CODE

class Solution {
    public int removeDuplicates(int[] nums) {
      //缓存,元素及对应出现的次数
    	Map<Integer,Integer> map = new HashMap<>();
      //新数组指针a,默认从下标0开始
      int slow = 0;
      int len = nums.length;
      for(int fast=1;fast<len;fast++){
        //老指针和新指针指向的值相同,则说明重复,新指针不动
        if(nums[fast]==nums[slow]){
          //判断是否存在,不存在或出现次数=1,则缓存结果值+1,
          Integer res = map.get(nums[fast]);
          //==null的case为 slow坐标为0,fast为1,这时候会出现相等但是取出的缓存值为null的情况,直接更新为2即可
          if(res==null||res==1){
            slow=slow+1;
            nums[slow] = nums[fast];
            //更新元素出现次数为2
            map.put(nums[fast],2);
          }
        }else{
          //新老指针指向的值不同,不重复,需要将新数组指针后移,并且设置新的值
          //后移
          slow=slow+1;
          //设置新的值
          nums[slow] = nums[fast];
          //设置出现缓存次数1,
          map.put(nums[fast],1);
        }
      }
      //因为slow是从0开始,最终计算长度,需要+1
      return slow+1;
    }
}

复杂度

  • 时间复杂度:O(N),N为数组长度,遍历一次数组长度
  • 空间复杂度:O(1) ,在数组本身赋值计算,无其他内存空间开销

结果

  • 执行用时:2 ms, 在所有 Java 提交中击败了9.88%的用户

    内存消耗:38.6 MB, 在所有 Java 提交中击败了49.30%的用户

双指针优化解法

解题思路

双指针缓存方式解题出来后,看了下执行用时击败9.88%,陷入了沉思...,开玩笑,主要是觉得击败了这么少,应该是很笨重的解法了,所以去解法区又看了看,找到了第二种通用解法,之所以说通用是因为抽象比较好,不像我上面的那种bycase的解决,当然现在想想上面的解法也可以抽象出来,废话不多说,上思路

核心思路

为了让解法更具有一般性,我们将原问题的「保留 2 位」修改为「保留 k 位」。

对于此类问题,我们应该进行如下考虑:

==由于是保留 k相同数字,对于前 k 个数字,我们可以直接保留==

==对于后面的任意数字,能够保留的前提是:与当前写入的位置前面的第 k 个元素进行比较,不相同则保留==

举个🌰,我们令 k=2,假设有如下样例

[1,1,1,1,1,1,2,2,2,2,2,2,3]

首先我们先让前 2 位直接保留,得到 1,1

对后面的每一位进行继续遍历,能够保留的前提是与当前位置的前面 k 个元素不同(答案中的第一个 1),因此我们会跳过剩余的 1,将第一个 2 追加,得到 1,1,2

继续这个过程,这时候是和答案中的第 21 进行对比,因此可以得到 1,1,2,2

这时候和答案中的第 12 比较,只有与其不同的元素能追加到答案,因此剩余的 2 被跳过,3 被追加到答案:1,1,2,2,3

最后根据大佬的思路,自己写了一版本,执行由2ms,降为1ms,巨大的改进

CODE

class Solution {
    public int removeDuplicates(int[] nums) {
      	//慢指针
        int slow = 0;
        int len = nums.length;
        //快指针
        for(int fast=0;fast<nums.length;fast++){
            //前K个数字或者 快指针的元素值与慢指针的前K个位置的元素不相等,则保留
            if(fast < 2 || nums[slow-2] != nums[fast] ){
                nums[slow] = nums[fast];
                //保留后,右移
            	  slow++;
            }
        }
      	//每次保留后都进行++,所以跟上面的不一样,不需要再++,直接返回即可
        return slow;
    }
}

复杂度

  • 时间复杂度:O(N),N为数组长度,遍历一次数组长度
  • 空间复杂度:O(1)

结果

  • 执行用时:1 ms, 在所有 Java 提交中击败了66.89%的用户
  • 内存消耗:38.7 MB, 在所有 Java 提交中击败了38.47%的用户

优雅,永不过时!