完成任务罢了(参加一个课程的任务)
数组题目
删除有序数组中的重复项
最原始的思路
其实这是很早之前写过的一道题,并不难,模拟就好了,但是,正因为写过,才更加清楚的知道了自己以前究竟有多菜。也明白了改进思路是多么重要的一件事情。
先说我以前的思路,就是强行模拟,先建立一个计数器($ count $),确定插入的位置,然后再设置一个输出 $ count1 ( 因 为 输 出 的 个 数 是 确 定 的 嘛 , s e t 一 下 就 好 ) , 每 读 到 一 个 开 始 没 有 读 取 的 元 素 , 就 将 这 个 元 素 插 入 , 然 后 计 数 器 加 一 , 同 时 , 为 了 防 止 越 界 , f o r 循 环 的 次 数 不 是 (因为输出的个数是确定的嘛,set一下就好),每读到一个开始没有读取的元素,就将这个元素插入,然后计数器加一,同时,为了防止越界,for循环的次数不是 (因为输出的个数是确定的嘛,set一下就好),每读到一个开始没有读取的元素,就将这个元素插入,然后计数器加一,同时,为了防止越界,for循环的次数不是 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[fast−1]!=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 fast−slow>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 i、j、pointer,就相当于已经排序了,还给强迫症的朋友们省去了一步排序。至于为什么不直接写等于0或者大于0,当然是做其它题防爆栈养成的习惯。当然,网上也有大佬用哈希表做的,但是我不会,这里就不写了,链接 放在这里,大家自己去看。