leetcode力扣_无用的束缚_26. 删除有序数组中的重复项_(暴力、双指针)

题目:

在这里插入图片描述
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/remove-duplicates-from-sorted-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处

在这里插入图片描述

题目分析

通过题目,我们可以了解到,这时对一个数组删除重复的元素的操作。并且,题目给出了一些关键条件:
1、不能额外开辟数组,只能在原处删除,意思就是不能使用map、set等容器和散列表进行去重。
2.数组是有序的,因此可以得出,每个相同的元素是相邻的。

因此我们有两种解题选择:
1、通过c++的vector容器对数组中重复的元素进行删除。(因为是数组,所以对其进行增删的时间复杂度较高)
2、双指针,通过前指针确定每个不重复的值,后指针跳过重复的值,去查找剩下不重复的值,并赋值到前指针确定好的值的后一格。

我们先看第一个暴力方法的代码:

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        //直接暴力删除,从第二个元素开始是因为要和前一个元素进行比较,防止数组越界
        //通过迭代器访问vector数组,因为erase函数要求传入的参数的迭代器,可以让erase函数使用的更加方便
        for(vector<int>::iterator pi = nums.begin() + 1;pi != nums.end();){
            //当遇到了一个数与前一个数相同,就将当前这个数删除
            if((*pi) == (*(pi - 1))){
            //vector容器中的erase函数会返回一个指向删除的元素的下一个元素的迭代器
              pi = nums.erase(pi);
              continue;//因为上一行返回了下一个元素的迭代器,就不需要后序的往后移动迭代器的操作了
            }
            ++pi;//迭代器后移
        }
        //将所有的重复元素删除完后吗,返回删除所有重复项后的数组的长度
        return nums.size();
    }
};

在这里插入图片描述

这时我们可以通过评测数据看出,这样暴力删除的代码十分低效


代码分析:

为什么上面这种方法需要那么多的时间呢,这和数组的结构有关。
因为数组是线性表,存储的元素都是连续的,当删除一个一个元素时,就要将后面所有的元素都往前移动一格。如果需要删除n次,那没次就需要移动m个元素。所消耗的时间就是n*m。所以说非常低效。
那我们是否有更加高效的方法对数组进行操作呢?
让我们往下看…


算法优化:

双指针 ->前后指针(注意,此时所说的指针都是数组的下标)
我们都知道,双指针可以在特殊的情况下将O(n2)的复杂度降低到O(n)的复杂度。因此我们可以采用这个算法,对数组进行操作。

首先我们定义一个前指针 i ,它用来存储被确定好的,不重复的最后一个元素的下标。默认初始化为0,因为删除重复的元素是从第二个元素开始删除。
然后我们 定义一个后指针 j ,它用来扫描i后面的元素,忽略与 i 元素值相同的元素。
当j扫描到第一个与 i 指向的值不相同的元素时,说明 j 指针已经将与 i 相同的所有元素全部跳过了,此时我们就可以把这个不相同的元素放在i的后面,这样,我们就确定了第二个唯一的元素。

初始状况:

val1123
pointij
index01234

j 向后扫描:

val1123
pointij
index01234

此时的j与i指向的值不相等,然而 ij 中间的元素就是j跳过的值
我们可以将 j 指向的元素赋值给i的下一个元素的位置
此时 i 往后移动一位,并且 j 也要往后移动一位
得到如下状态:

val11 223
pointij
index01234

这时我们又发现,i 指向的元素与 j 指向的元素的值不相等
我们继续将 j 指向的值赋值给 i 的后一个元素
得到如下状态:

val11 22 33
pointij
index01234

此时,j 就将数组完整的遍历完了一遍,并且,已经将所有的不重复的值放到了数组的最前面,i 指针指向的元素之前的元素(包含i),就是已经通过筛选确定好的所有的不重复的数组元素,i + 1就是这些元素的总个数(因为数组下标从0开始),因此我们只需要将 i 的值返回即可

接下来我们看代码:

int removeDuplicates(int* nums, int numsSize){
    //双指针
    int i = 0;//将第一个数作为前指针
    int j = 1;//第二个数作为后指针
    //通过j指针遍历整个数组
    while(j < numsSize){
        //后指针j一直往后遍历
        //当遇到一个元素与前指针的元素不相同的时候
        if(nums[j] != nums[i]){
            //就将前指针后移一位
            //然后让这个与前指针不同的元素赋值给前指针
            ++i;
            nums[i] = nums[j];
        }
        //此时后指针继续往后遍历
        ++j;
    }
    return i + 1;
}

代码采用的是c语言,比较简洁明了。
我们可以通过代码和思路发现,我们只通过 j 对数组进行了一次遍历,就完成了题目的任务。极大的优化了第一个方案的代码,远远减少了运行所需的时间。

总结

任何问题,都有合适的解决方法,并且世界上没有万能的方法。算法为人所创,而造富于人。虽然这个题解讲解的并不是多么详细,举例的数据也比较短,但是在以后我会争取将将自己的思路能够用更加通俗易懂的方式分享给大家!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangyanping987

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值