题目
删除有序数组中的重复项 II
公众号 《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
坐标为0
,fast
为1
,这时候会出现相等但是取出的缓存值为null
的情况,直接更新为2
即可
- 当
nums[i] != nums[a]
,元素不相等,a
指针后移,后移后赋值新的nums[a]
- 更新元素出现次数缓存为
1
- 指针继续遍历
- 遍历结束
- 当
- 返回结果
a+1
,因为a
从0
开始算起,最终计算长度需要+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
继续这个过程,这时候是和答案中的第 2
个 1
进行对比,因此可以得到 1,1,2,2
这时候和答案中的第 1
个 2
比较,只有与其不同的元素能追加到答案,因此剩余的 2
被跳过,3
被追加到答案:1,1,2,2,3
最后根据大佬的思路,自己写了一版本,执行由2
ms,降为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
%的用户
优雅,永不过时!