目录
题目链接:
注:下述题目描述和示例均来自力扣
题目描述
给你一个有序数组 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 * 10^4
-104 <= nums[i] <= 104
nums
已按升序排列
解法一:双指针
1. 问题背景
- 输入是一个有序数组
nums
。 - 要求删除重复超过两次的元素,使得每个元素最多保留两次。
- 必须在原地修改数组,并且只使用 O(1) 的额外空间。
- 返回值是新数组的长度,同时通过原数组的前
length
个元素表示修改后的结果。
2. 核心思路
由于数组是有序的,重复的元素必然连续排列。因此,我们可以用两个指针:
left
指针:用于记录当前可以放置有效元素的位置。right
指针:用于遍历整个数组,寻找符合条件的元素。
算法的关键在于:
- 如果某个元素与
left - 2
位置的元素不同(即它不是第三个或更多次重复的元素),则将其放入left
指针的位置。 - 否则,跳过这个元素(因为它是多余的重复项)。
3. 实现步骤
(1) 初始条件判断
if(len <= 2){
return len;
}
- 如果数组长度小于等于 2,则直接返回数组长度,因为最多只有两个元素,不可能有重复超过两次的情况。
(2) 初始化指针
int left = 2;
int right = 2;
left
和right
都初始化为 2(索引从 0 开始)。- 原因是:数组的前两个元素无论如何都不会被删除(最多保留两次),所以可以直接从第三个元素开始处理。
(3) 双指针遍历
while(right < len){
if(nums[left - 2] != nums[right]){
nums[left] = nums[right];
left++;
}
right++;
}
right
指针:从索引 2 开始,逐一遍历数组中的所有元素。left
指针:指向当前可以放置有效元素的位置。- 核心判断条件:
if(nums[left - 2] != nums[right])
- 如果
nums[left - 2]
与nums[right]
不相等,说明nums[right]
是一个有效的元素(不是第三次或更多次重复的元素)。 - 此时将
nums[right]
放入nums[left]
的位置,并将left
指针向后移动一位。
- 如果
- 否则:
- 如果
nums[left - 2] == nums[right]
,说明nums[right]
是多余的重复元素,直接跳过,不进行任何操作。
- 如果
(4) 返回结果
return left;
- 最终,
left
指针的位置就是新数组的有效长度,因为left
指针始终指向下一个可以放置有效元素的位置。 - 数组的前
left
个元素就是修改后的结果。
4. 示例解析
示例 1
输入:nums = [1,1,1,2,2,3]
执行过程:
- 初始状态:
left = 2
,right = 2
- 第一次比较:
nums[left - 2] = nums[0] = 1
,nums[right] = nums[2] = 1
,相等,跳过。 - 第二次比较:
nums[left - 2] = nums[0] = 1
,nums[right] = nums[3] = 2
,不相等,替换并移动left
。 - 结果:
nums = [1,1,2,2,3,...]
,left = 5
输出:5, nums = [1,1,2,2,3]
示例 2
输入:nums = [0,0,1,1,1,1,2,3,3]
执行过程:
- 初始状态:
left = 2
,right = 2
- 第一次比较:
nums[left - 2] = nums[0] = 0
,nums[right] = nums[2] = 1
,不相等,替换并移动left
。 - 第二次比较:
nums[left - 2] = nums[1] = 0
,nums[right] = nums[3] = 1
,不相等,替换并移动left
。 - 第三次比较:
nums[left - 2] = nums[2] = 1
,nums[right] = nums[4] = 1
,相等,跳过。 - ...
- 结果:
nums = [0,0,1,1,2,3,3,...]
,left = 7
输出:7, nums = [0,0,1,1,2,3,3]
Java写法:
class Solution {
public int removeDuplicates(int[] nums) {
// 有序数组
int len = nums.length;
// 如果本来就不超过两个那就直接返回答案
if(len <= 2){
return len;
}
// 直接从索引2也就是第三个位置开始判断
// right用于寻找不同的值
// left用于记录替换的位置
// left - 2的位置用于跟right比较
int left = 2;
int right = 2;
while(right < len){
// 看是否是同一元素
if(nums[left - 2] != nums[right]){
// 不是,替换并且更新替换的位置
nums[left] = nums[right];
left++;
}
// right指针一直寻找
right++;
}
// 最后left 的位置就最终的索引停留的位置的后面,因为他最后又执行了一次+1操作
return left;
}
}
C++写法:
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int len = nums.size();
if(len <= 2){
return len;
}
int left = 2;
int right = 2;
while(right < len){
if(nums[left - 2] != nums[right]){
nums[left] = nums[right];
left++;
}
right++;
}
return left;
}
};
运行时间
时间复杂度和空间复杂度
- 时间复杂度:O(n),其中 n 是数组的长度。
right
指针遍历整个数组一次。 - 空间复杂度:O(1),只使用了常数级别的额外空间。
总结
我嘞个双指针的操作啊,其实这个题都是这样的模版,数组的这种操作还是太简单了。