数组的题目

本文介绍了如何通过算法优化解决数组中的重复项删除、元素移除以及三数之和问题。对比了原始低效的解决方案,如使用插入操作导致的时间复杂度为O(n^2),并详细阐述了采用双指针技巧改进后的高效算法,显著降低时间复杂度。此外,还探讨了排序在解决数组问题中的应用,如三数之和问题中利用排序减少判断次数。
摘要由CSDN通过智能技术生成

完成任务罢了(参加一个课程的任务)

数组题目

删除有序数组中的重复项

在这里插入图片描述

最原始的思路

  其实这是很早之前写过的一道题,并不难,模拟就好了,但是,正因为写过,才更加清楚的知道了自己以前究竟有多菜。也明白了改进思路是多么重要的一件事情。

  先说我以前的思路,就是强行模拟,先建立一个计数器($ count $),确定插入的位置,然后再设置一个输出 $ count1 ( 因 为 输 出 的 个 数 是 确 定 的 嘛 , s e t 一 下 就 好 ) , 每 读 到 一 个 开 始 没 有 读 取 的 元 素 , 就 将 这 个 元 素 插 入 , 然 后 计 数 器 加 一 , 同 时 , 为 了 防 止 越 界 , f o r 循 环 的 次 数 不 是 (因为输出的个数是确定的嘛,set一下就好),每读到一个开始没有读取的元素,就将这个元素插入,然后计数器加一,同时,为了防止越界,for循环的次数不是 setfor len(nums) , 而 是 ,而是 len(nums) + count1 $。


class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        count = 0
        count1 = len(set(nums))
        for i in range(len(nums)+count1):
            if nums[i] not in nums[0:i]:
                nums.insert(count,nums[i])
                count += 1
        return count1             

  现在看这串代码简直是公开处刑,一点算法思想也没有不说,还反复调函数,要知道一个 i n s e r t insert insert的时间复杂度可是 O ( n ) O(n) O(n),就相当于这串代码的时间复杂度是 O ( n 2 ) O(n^2) O(n2),这里的$n = len(nums) + count1 $,而且,其实就这串的写法来说,这也不是最好的写法。


class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        count = 0
        count1 = len(set(nums))
        for i in range(len(nums)+count1):
            if nums[i] not in nums[0:i]:
                nums.insert(count,nums[i])
                count += 1
                if count == count1:
                    return count1

  这样写,虽然时间复杂度没变,但是时间会消耗少一点。(格式就不提了,太丑了,赋值都写了两行)

再次遇见

  再次遇见这道题,其实距离我上一次遇见是7个月之后了,是我参加了一个Leetcode刷题群遇见的,老实讲,当我打开它之前,我都忘记我以前做过这道题了,但是瞄了一眼以前花的时间,好家伙 1000ms往上了,简直吓死人。

  当然,之前自己是个什么半吊子心里也还是清楚,所以还是有信心比上次写得好。先仔细分析一下题,其实题目意思就是让我们输出前面几个数,且不重复。那其实就是将不重复的元素交换到前面来,因为删除的话,也还是蛮消耗时间的,然后抽取关键信息——数组、交换,那当然毫不犹豫的双指针,再看遍历顺序,要查找重复项,那肯定至少起点是接近的,那就肯定是快慢指针了,了解了方法之后,那就再处理细节。

  由于第一个元素(也就是数组中的0号元素)一定是不会在第一项重复的,所以我们可以将指针的起点设置在第二个元素(数组的1号元素)。然后 f a s t fast fast指针向前迭代,如果它的前一项元素不等于该项元素 n u m s [ f a s t − 1 ] ! = n u m s [ f a s t ] nums[fast-1] != nums[fast] nums[fast1]!=nums[fast],就继续向前迭代,否则,将 f a s t fast fast指针的值赋给 s l o w slow slow指针,然后 s l o w slow slow指针加一,同时,由于 s l o w slow slow项之前已经完全包含了除当下 f a s t fast fast之外 f a s t fast fast已经指向的所有元素,所以不用担心覆盖的问题。最后, f a s t fast fast一定会先到终点,不能越界,所以 f a s t < n fast < n fast<n。代码如下:


class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        slow = fast = 1
        n = len(nums)
        while fast < n:
            if nums[fast-1] != nums[fast]:
                nums[slow] = nums[fast]
                slow += 1
            fast += 1
        return slow

  当然,我们说了,快慢指针起点相近,那当然肯定不是只有相同的起点,也有错位的起点:

  起点为0、1,然后向前迭代,如果两个指针元素不相等,同时 f a s t − s l o w > 1 fast-slow>1 fastslow>1,就说明 f a s t fast fast指向的元素出现了重复,同时,由于 s l o w slow slow是已经操作过(其实就是 s l o w slow slow为0的时候相当于已经去重了,所以需要操作的元素位置需要向后移位),那么令 n u m s [ s l o w + 1 ] = n u m s [ f a s t ] nums[slow+1]=nums[fast] nums[slow+1]=nums[fast]即可。同样的, f a s t fast fast不能越界。


class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        slow, fast = 0, 1
        n = len(nums)
        while fast < n:
            if nums[fast] != nums[slow]:
                if fast - slow > 1:
                    nums[slow+1] = nums[fast]
                slow += 1
            fast += 1
        return slow + 1

移除元素

在这里插入图片描述

  这个题也是做了好久了,第一次通过也是七个月前,但是我再次写完,后看了一眼,七个月的时间,写出来的代码基本没差,都是双指针中的快慢指针(所以我七个月前到底经历了啥,为什么同样的时间写出来的代码差这么多?)。

  方法同上,只不过由于我们不知道 v a l val val值出现的位置,所以指针必须从 0 0 0号元素开始,一旦发现不等于 v a l val val值的元素,就将它赋值到 s l o w slow slow去,同样的,slow之前的元素都是被操作过的(已经赋值过了),不必担心掩盖的问题,当然, f a s t fast fast不能越界。


class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        fast = slow = 0
        n = len(nums)
        while fast < n:
            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow += 1
            fast += 1
        return slow

三数之和

在这里插入图片描述

  摊牌了,不装了,,就是你,不要往下看了,就是现在划手机的你,说吧,最开始是不是套的三重循环,超时不说,还被各种限制条件折磨。最后还弄不清楚。

  好吧,其实这道题也没有看上去那么难,首先我们看给定的数组,不是规律数组,那就加大了我们对可能重复元素判断的难度(思路简单,操作复杂),所以先对数组进行排序,这样,不同指针指向的元素如果相等,就可以不再判断,帮我们减少了一部分运行时间。

  其实到这里,我们就可以写循环:


n = len(nums)
for i in range(n):
    if i > 0 and nums[i] != nums[i-1]:
        for j in range(i+1, n):
            if j > i + 1 and nums[j] != nums[j-1]:
                for k in range(j+1, n):
                    if k > j + 1 and nums[k] != nums[k-1]:
                         if ...   # 判断和是否为0

  虽然我没这么写过,但猜测大概是不会超时的,但其实这个代码还有优化的空间(也还有变好看的空间,这循环和判断,简直是劝退神器)。我们可以发现,数组是从小到大排列的,如果某个值加前面两个指针指向元素之和小于0了,那它之前的数就不用判断了,因为一定会小于0,所以,我们将最后一个指针 p o i n t e r pointer pointer从大到小遍历,然后再把上面的 i f . . . ! = . . . if ...!=... if...!=...借助break、continue优化一下(没什么实际作用,甚至执行的行数变多了,但是好看)。


class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        res = []
        nums.sort()
        for i in range(len(nums)):
            if i > 0 and nums[i] == nums[i-1]:
                continue
            pointer = len(nums) - 1
            for j in range(i+1, len(nums)):
                if j > i + 1 and nums[j] == nums[j-1]:
                    continue
                while pointer > j and nums[i] + nums[j] > -nums[pointer]:
                    pointer -= 1
                if j == pointer:
                    break
                if nums[i] + nums[j] == -nums[pointer]:
                    res.append([nums[i], nums[j], nums[pointer]])
        return res

  最后添加元素的方式以 i 、 j 、 p o i n t e r i、j、pointer ijpointer,就相当于已经排序了,还给强迫症的朋友们省去了一步排序。至于为什么不直接写等于0或者大于0,当然是做其它题防爆栈养成的习惯。当然,网上也有大佬用哈希表做的,但是我不会,这里就不写了,链接 放在这里,大家自己去看。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值