今天开始正式进行LeetCode的刷题之旅,自己的编程算法能力都太差了,所以决定进行苦练,查到的代码和解法,优化以及个人的理解,就这样。
LeetCoddehttps://blog.csdn.net/TeFuirnever/column/info/36614
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
题解1:
我们首先想到的解法是通过三重循环,于是我就写出了如下代码:
class Solution:
def threeSum(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
from collections import Counter
k = []
result = []
for i, a in enumerate(nums):
for j, b in enumerate(nums[i + 1:]):
for _, c in enumerate(nums[j + i + 2:]):
if a + b + c == 0 and Counter([a, b, c]) not in k:
k.append(Counter([a, b, c]))
for i in k:
result.append(list(i.elements()))
return result
考虑结果重复的问题,我们通过collections.Counter记录所有数字出现的次数,如果前后有相同的话,我们就不添加到result中去,但是这样的话会超时,该方法不可行。
题解2:
class Solution:
def threeSum(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
res = []
nums.sort()
for i in range(len(nums) - 2):
# 排好序之后,如果nums[i]>0,说明后面的数全部大于0
if nums[i] > 0:
break
if i==0 or nums[i] > nums[i-1]:# 去重
left, right = i + 1, len(nums) - 1
# 剪枝
if nums[i] + nums[left] + nums[left+1] > 0 or nums[i] + nums[right - 1] + nums[right] < 0:
continue
while left < right:
ident = nums[i] + nums[left] + nums[right]
if ident==0:
res.append([nums[i],nums[left],nums[right]])
left += 1
right -= 1
# 去重
while left < right and nums[left]==nums[left - 1]:
left += 1
while left < right and nums[right]==nums[right + 1]:
right -= 1
elif ident < 0:
left += 1
else:
right -= 1
return res
这个算法是和我自己写的算法比较相似的,但是我没有好的办法解决去重复,所以就借用了这个算法。
主要是先确定i,for循环【len(nums)-2】次,然后对left和right进行两个方向的遍历,从最外侧向最内侧遍历,因为是排好顺序的列表,所以相同的元素在一块,正负也都有位置(左侧负数,右侧正数)。
要求的是a+b+c=0 其实就是要求a+b=-c,那么问题可以转化为依次遍历数组元素c,然后在剩下的数中做两数之和为-c的问题。问题在于如何简化算法以及优化复杂度。
1.首先可以先排序(O(nlogn)),这样保证数组有序之后可以利用大小关系判断。
2.设置两个指针left、right,分别从左边以及右边向中间遍历,如果找到a+b+c==0,那么可以将这个答案加入到答案集里,如果a+b+c<0,此时固定的是c,说明a+b太小了,因此left+=1;如果a+b+c>0,此时a+b过大,因此right-=1。
3.去重,这一步则是利用了有序性,如果两个数相同,那他们在数组的位置一定是相邻的(连着几个数相同也是可能的),因此去重的操作就能简单遍历一下相邻的是否相同即可。由于数组有序性使得去重这一步很简单,因此也可以看出第一步的作用。
此外还有一些小细节的地方,比如说当遍历到c>0的时候,由于之后的数都是正数,那三数之和一定大于0,就没必要继续遍历c了(因为继续向后遍历c只会更大,那之后的数加起来一定大于0); 或者固定c,如果c及其后面连着两个数a,b,他们的和已经大于0了,就没必要进行下一步的操作,此时遍历下一个c; 同理,如果c和数组最后两个数的和仍然小于0,也没必要进行下一步操作。
看到一个专栏的解法和优化,好好学习:Leetcode 15:三数之和(最详细解决方案!!!)
题解3:
nums_hash = {}
result = list()
for num in nums:
nums_hash[num] = nums_hash.get(num, 0) + 1
if 0 in nums_hash and nums_hash[0] >= 3:
result.append([0, 0, 0])
neg = list(filter(lambda x: x < 0, nums_hash))
pos = list(filter(lambda x: x>= 0, nums_hash))
for i in neg:
for j in pos:
dif = 0 - i - j
if dif in nums_hash:
if dif in (i, j) and nums_hash[dif] >= 2:
result.append([i, j, dif])
if dif < i or dif > j:
result.append([i, j, dif])
return result