一、题目
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
做题可以直接到删除排序数组中的重复项
示例
输入
[1,1,2]
输出
2
解释
原数组的前两个元素被修改为 1, 2
二、解题
LeetCode只需要提交核心函数部分,输入输出都已经写好了。所以要明确给定函数的输入和输出。本题中函数的参数是一个vector,要了解vector的基本操作。
我在做题时会自己在本地练习,然后把核心函数按照LeetCode给定的函数做对应修改,所以我会给出两版代码,一个是我在本地练习时的代码(完整,可以运行),一个是我提交的程序。
vector的基本操作
vector的声明vector<int> v;
vector的插入v.push_back(a)
(a是一个int类型的变量)
获取vector中的元素个数v.size()
获取vector中第i个元素v[i]
以上参考数组长度的获得 vector的用法
解题思路1
因为数组是排好序的,所以如果存在重复项,一定是相邻项。所以,从第一个元素开始依次遍历数组元素,确认后一项的值是否与其相等,如果相等,则后边所有的项向前复制一步。
本地代码
先输入n,接下来输入n个有序且可能重复的整数,存入数组nums。
输出不重复的元素个数len,及删除重复元素后的数列。
#include <cstdio>
int n, nums[2000];
int removeDuplicates(){
int len = n;
for(int i=0; i<len-1; i++){
if(nums[i+1]==nums[i]){
for(int k=i+1; k<len-1; k++)
nums[k] = nums[k+1];
i--;
len--;
}
}
return len;
}
int main(){
scanf("%d", &n);
for(int a=0; a<n; a++)
scanf("%d", &nums[a]);
int len = removeDuplicates();
printf("%d\n", len);
for(int a=0; a<len; a++)
printf("%d ", nums[a]);
printf("\n");
return 0;
}
提交代码
int removeDuplicates(vector<int>& nums) {
int len = nums.size();
for(int i=0; i<len-1; i++){
if(nums[i+1]==nums[i]){
for(int k=i+1; k<len-1; k++)
nums[k] = nums[k+1];
i--;
len--;
}
}
return len;
}
需要注意
i --;
遍历到第i个元素,发现第i+1个元素与第i个元素重复,i+1后边的元素前依次前移,相当于把i+1元素删除了,但是接下来原来的第i+2个位置(移动以后的第i+1个位置)也可能与第i个元素重复,所以依然需要从第i个元素开始遍历。而在for循环中一次循环结束i会加一,所以需要先减一,以保证下轮循环依然是从原来的i值开始。len--;
len初始化为数组长度,每删除一个元素(后边的元素往前移动一次),len需要减一。- 复制时
从前往后,把第k+1个元素赋值到第k个位置,原来第k个位置的元素是重复值或者已经被往前复制了的值,即使被覆盖了也没有关系。
【扩展】插入排序时,需要从后往前执行复制,第i个元素需要插入第a个位置完成排序,a到i-1位置的元素挨个后移一位,因为第i个元素已经被其后一位位置保存,第i-1个元素复制到第i个位置时把第i个位置覆盖掉也没有关系。
解题思路2
i从0开始遍历,j从i+1开始。j一直后找,直到找到第一个与第i个元素不相等的元素,把第j个元素前移到第i+1的位置,之后的元素依次前移。此时数组中第i个元素是唯一的了。i向后移动一位,j变为i+1的位置,重复上述操作。如果相邻的两个元素不等,也就是nums[i] != nums[j]
则i和j都向后移动一位。
本地代码
#include <cstdio>
int n, nums[2000];
int removeDuplicates(){
int len = n;
if(len<=1) return len;
int i=0, j=1;
while(i<len){
while(j<len&&nums[j]==nums[i]) j++;
if(j>i+1){
int t=i+1, k=j;
while(k<len)
nums[t++] = nums[k++];
len = len - (j-i-1);
i++; j = i+1;
}
else{
i++; j++;
}
}
return len;
}
int main(){
scanf("%d", &n);
for(int a=0; a<n; a++)
scanf("%d", &nums[a]);
int len = removeDuplicates();
printf("%d\n", len);
for(int a=0; a<len; a++)
printf("%d ", nums[a]);
printf("\n");
return 0;
}
提交代码
int removeDuplicates(vector<int>& nums) {
int len = nums.size();
if(len<=1) return len;
int i=0, j=1;
while(i<len){
while(j<len&&nums[j]==nums[i]) j++;
if(j>i+1){
int t=i+1, k=j;
while(k<len)
nums[t++] = nums[k++];
len = len - (j-i-1);
i++; j = i+1;
}
else{
i++; j++;
}
}
return len;
}
别人的题解
该题的题解中很多人提到__“双指针法”__,然后去学习了一下。
双指针法,即在解题时使用两个指针(游标)同向或者对向共同遍历数组。
【扩展】
双指针法适用的问题:
- 有序数组中找到两个加和等于特定值的元素;
- 两个有序链表的合并;
- 快速排序;
- 将数组中的奇数都放在左边,偶数都放在右边。
- 求单链表中的中间值。
参考
看了别人的题解,对比自己的解题思路,发现我每次都会把数组中剩余的元素往前复制,导致我的算法复杂度是O(n^2),其实没有必要,只需要把不重复的那一个数往前复制就好了。
具体的讲解可以看这个——【双指针】删除重复项-带优化思路,很清晰。
再复制一份别人的超简洁解法供自己学(mo)习(bai)。
int removeDuplicates(vector<int>& nums) {
if (nums.size() < 2) return nums.size();
int j = 0;
for (int i = 1; i < nums.size(); i++)
if (nums[j] != nums[i]) nums[++j] = nums[i];
return ++j;
}
来自评论区
三、写在最后
以下是废话。
今天开始刷题,并不知道习惯知难而退的我能坚持多久。
但愿自己能多做点题,写成一个系列。
写这个刷题记录主要希望自己能思考和总结,而不是提交了通过了这题就结束了。