1.题目描述:
? ? ? ?给你一个 升序排列 的数组 nums ,请你原地删除重复出现的元素,使每个元素只出现一次 ,返回删除后数组的新长度。元素的相对顺序 应该保持 一致 。
? ? ? ?由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么?nums?的前 k 个元素应该保存最终结果。将最终结果插入?nums 的前 k 个位置后返回 k 。不要使用额外的空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/remove-duplicates-from-sorted-array
2.编程思路:
? ? ? ? 对于数据结构题目的编程思路,我建议大家直接画图,画图能够帮助我们更全面地思考问题。首先明确目标是删除有序(升序)数列的重复项。如果我们不考虑时间和空间复杂度,那么最先想到的就是方法1——遍历删除。也就是定义两个序号Start从0开始、End从1开始,如果两者指向的元素相等就说明有重复数据出现,然后将End和以后的数据都向前挪动,直到End指向数组的最后一个元素,如图1所示。
图1
? ? ? ?这种方法能够完成功能,但是并不能满足要求,因为时间复杂度为O(n^2)。所谓时间复杂度就是执行简单代码的次数,上述方法在最坏的情况下(全部都是一样的数据)需要移动(也就是值覆盖)n-1、n-2、...、2、1,利用等差数列求和可以求出一共需要有n*(n-1)/2次赋值操作,所以时间复杂度就是O(n^2)。没有开辟新的空间,所以空间复杂度是O(1)。
? ? ? ?方法2:我们重新开辟一个数组,也用两个序号进行操作,Src遍历指向原数组,Dst遍历指向新开辟的数组。不同的是,我们不移动后面的所有值,而是只要Src和Dst所指向的数据不同就将Src所指向的数据赋给Dst所指向的空间,这里的细节一定要注意,谁走在前面,谁赋值给谁。如图2所示。
?图2
? ? ? 这种方法也能实现功能,但是空间复杂度是O(n),不能满足要求。
? ? ? ? 方法3:我们可以从上面两种简单粗暴的方法中吸取优点,就是让两个序号去遍历,最后将满足条件的的值赋给自己,也就是数组内的值覆盖。如图3所示,我们让Start和End两个序号去遍历原数组,如果两个不等就将Start所指向的数据赋给Dst所指向的空间,这也是所谓的三指针,但是成作序号更好理解。这里有一个细节就是会有End越界的时候,这时候就有最后两个数是相等还是不等两种情况,但是不管是哪种情况都应该把Start所指向的数据赋给Dst所指向的空间。(这点大家画图好好想一下,虽然简单,但是是细节!)
?图3
? ? ? ? 时间复杂度:这个相当于执行n次条件赋值操作,所以就是O(n),空间复杂度为O(1)。
3.代码
int removeDuplicates(int* nums, int numsSize){
// 处理空数组整个特殊情况
if(numsSize == 0)
{
return 0;
}
// 定义三个序号
int Start = 0;
int End = 1;
int Dst = 0;
while(End<numsSize)
{
// Start End 所指向的数据不等时值覆盖
if(nums[Start]!=nums[End])
{
nums[Dst]=nums[Start];
End++;
Start++;
Dst++;
}
// 相等时继续遍历不赋值
else
{
Start++;
End++;
}
}
// 考虑End已经越界的时候
nums[Dst] = nums[Start];
Dst++;
return Dst;
}
4.总结
? ? ? ? 对于C语言的数据结构,最重要的就是画图(这能够帮助理清逻辑)、分析复杂度、敲代码!
|