力扣 1 两数之和 2 两数相加 15 三数之和
1.两数之和
题目意思比较简单,最沙雕双重for循环呗但是就超时啦,所以上来首先想到的是用排序后用双指针首尾查找的方法啦时间复杂度就是O(nlogn),编写过程中有几个python的点不太熟悉或者忘记啦总结如下:
1.如何get原数组元素按照大小排序的id呢,这里可以用python中自带的sorted函数了(sorted函数可以对所有可迭代对象排序,sort函数是内置在list里的,直接对原数组操作)
sort_id = sorted(range(length), key=lambda k: nums[k])
其中第一个参数为可迭代对象啦,第二个key即排序的规则,这里用了lambda表达式,回忆一下lambda匿名函数表达式 :前为参数后为return的值。 (lambda x:x+1)
有了原数组元素按照大小的id后就可以设置双指针啦,从sort_id的首尾开始设置双指针遍历吧。
后来发现了新解法,直接利用python中自带的字典结构(类似hash)的方法可以实现O(n)的复杂度。
即我一遍遍历,构造字典其中元素为key,下表为value,那么每次判断如果当前元素与target之差这个key存在的话就直接return,不存在的话就存储这个新的元素。这样可以一遍完成。
其中python中判断字典中是否存在key的方法即 x in dic.keys()即可。
2.两数相加
嗯虽说是中等题吧不过较为简单考察的就是基本操作,我的思路是首先对两个链表进行遍历,然后分别存储在list里面,然后拖过遍历list得到两个int型的整数,然后相加得到最终的值后通过将int转string类型反向遍历生成新的链表返回表头节点即可。
1. 这里在从list变成int整数中由于位数的原因需要用到乘方,即pow(a,b)代表a的b次方,这个忘记了嘻嘻重温一下。
2.我之所以转成str,其实在最早是想直接通过求模运算拆分位数的但是调试的时候发现如果末尾是0或者中间某位为0将出现问题,所以还是转成str,然后通过对str从后往前遍历进行存储:
(1)其中迭代对象逆序的方式我以为是直接range(n,0),结果发现不对,百度后明确了是range(n,-1,-1),即从n往后每次-1嘛直到0对应的下标,以后需要注意。
(2)我在链表如何链接下一个node上用的是将所有node存储在一个list里面,根据for循环的下标每次取出list末尾最后一个node链接到当前迭代的值然后再存入到list中,最后返回list的头元素。后来发现可以有更便捷的方法,即在迭代前先创建一个头节点用于最后返回,然后复制这个头节点到一个新的命名中,在迭代过程中每次先创建next节点,然后再另当前节点等于next节点直到迭代结束,这样可以节省空间复杂度。具体伪代码如下:
re = ListNode(0)
r = re
re.next = r
for 迭代过程:
r.next = ListNode(当前迭代的value)
r = r.next
return re.next
OK!上述是我的思路,然后看了一些人的题解,发现了按位相加记录进位数直接创建新节点的方法,看着代码比我的简介就复现了一下发现好像时间复杂度更高了哎。。。
我复现的代码:
re = ListNode(0)
carry = 0
r = ListNode(0)
re.next = r
while l1!=None or l2!=None :
x1 = l1.val if l1!=None else 0
x2 = l2.val if l2!=None else 0
x = x1 + x2 + carry
carry = x //10
r.next = ListNode(x%10)
r = r.next
if l1!=None:
l1 = l1.next
if l2!=None:
l2 = l2.next
if carry!=0:
r.next = ListNode(carry)
return re.next.next
嘻嘻还是回忆了一下三元表达式,x=a if ...else b
然后这是两次AC的结果
嗯 没差多少吧哈哈哈。
15.三数之和
这道题其实跟第一题比较相似,我的思路也是比较随大众的就是先对原数组进行排序,然后从最小的元素开始,即固定一个数,然后按照首尾双指针查找的方法查找后续子数组中是否存在两数之和等于这个元素的负数(这不就跟第一题一样嘛!target就是这个元素的负数)。下面四编写中的一些问题:
1.首先呢首先题目要求不能有重复元素,也就是说排序后呢只要相邻元素相等就需要跳过了,这里就很简单如果不等于0就判断是否相邻元素相等,等于直接continue就行了。
2.我上来写的第一遍程序在首尾双指针查找的时候是这样写的,如果当前双指针指向的元素等于target就直接跳出循环,那么存在的问题就是对于一个元素如果后面有两组以上的可行解就只会返回一组,所以后续为了将所有的可行解全部找出来就需要后续left与right一直走到底,即while的条件是只要left<right就一直找可行解。
3.?改好后题目的测试用例就通过了,然后我提交了一遍有问题了。错误用例[0,0,0,0]被我返回了两次[0,0,0],[0,0,0],但实际只有一次,检查程序后发现了我只判断了第一个元素不能重复,但是在后面的两数相加还是有问题呀,尤其是找到一组可行解后如果后面一个值跟这个值是一样的那不就又返回了一遍吗!这里有两种改法,第一种就是在往结果list中append这个新的元素时候加一个判断 即 元素 not in 结果数组,再添加,可以避免这个,但是这种方法直接导致了我最后313个测试用例只通过了311个,大范围的查找超时!第二种就比较简单,也就是在添加后对于left与right指针遍历加判断条件,即如果left后面的元素与其相同或者right前面元素与其相同就一直往前更新。这样可以通过所有的测试用例啦。
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums = sorted(nums)
length = len(nums)
redata = []
for i in range(length-2):
# 相同元素只在第一次出现的时候操作一次
if nums[i] == nums[i-1] and i > 0: continue
j = i+1 #左指针
k = length-1 #右指针
suit = 0 - nums[i]
while j < k:
if nums[j] + nums[k] == suit:
redata.append([nums[i], nums[j], nums[k]])
j += 1
k -= 1
while nums[j-1] == nums[j] and j < k: j += 1
while nums[k+1] == nums[k] and j < k: k -= 1
elif nums[j] + nums[k] < suit:
j += 1
while nums[j - 1] == nums[j] and j < k: j += 1
elif nums[j] + nums[k] > suit:
k -= 1
while nums[k + 1] == nums[k] and j < k: k -= 1
return redata