'''
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,
使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
'''
class Solution:
'''简单的双向指针算法:未找到解时,左右边界每次只移动1位
执行用时: 760 ms, 在所有 python3 提交中击败了91.80%的用户
内存消耗: 17.0 MB, 在所有 python3 提交中击败了23.80%的用户
'''
def threeSum(self, nums: list) -> list:
n = len(nums)
if nums is None or n < 3:
return []
nums.sort()
i = 0
ans = []
while i < n and nums[i] <= 0:
L, R, t = i + 1, n - 1, -nums[i]
while L < R:
if nums[L] + nums[R] < t:
L += 1 #和太小,左边界右移1位
elif nums[L] + nums[R] > t:
R -= 1 #和太大,右边界左移1位
else: #恰好相等,保存解
ans.append([nums[i], nums[L], nums[R]])
while L < R and nums[L] == nums[L+1]:#跳过左侧相同元素
L += 1
while L < R and nums[R] == nums[R-1]:#跳过右侧相同元素
R -= 1
L, R = L + 1, R - 1 #更新左右边界
i += 1
while i < n and nums[i] == nums[i-1]:#跳过相同的nums[i]
i += 1
return ans
'''另一种的双向指针算法(按理说效率更高,但实际用时比第一种多):
使用对分查找算法寻找合适的右边界(最左侧的解或者离解最近的较大元素值下标)。
也许是因为需要在右侧对分查找解的缘故,效率不如想象的高。
执行用时: 1344 ms, 在所有 python3 提交中击败了33.35%的用户
内存消耗: 17.0 MB, 在所有 python3 提交中击败了23.80%的用户
'''
def threeSum2(self, nums: list) -> list:
def bsearch(arr, low, high, key):
while low <= high:
mid = (low + high) // 2
if arr[mid] < key:
low = mid + 1
else: #若有解,返回最左侧的key的下标
high = mid - 1
return low #若未找到key,则返回第一个比key大的元素下标
n = len(nums)
if nums is None or n < 3:
return []
nums.sort()
i = 0
ans = []
while i < n and nums[i] <= 0:
L, R, t = i + 1, n - 1, -nums[i]
while L < R:
if nums[L] + nums[R] >= t: #可能存在解或者和太大
R = bsearch(nums, L+1, R-1, t-nums[L]) #寻找最接近t-nums[L]的元素下标
if nums[L] + nums[R] == t:
ans.append([nums[i], nums[L], nums[R]])
R -= 1 #只有找到解或者和太大时,才移动右边界
L += 1 #不管哪种情况都要移动左边界
i += 1
while i < n and nums[i] == nums[i-1]:#跳过相同的nums[i]
i += 1
return ans
'''非常高效的算法:使用字典来消除重复元素,并设置两个列表分别存储负数和非负数。
只需二重循环扫描2个有序列表,效率大大提高。
执行用时: 388 ms, 在所有 python3 提交中击败了98.30%的用户
内存消耗: 17.5 MB, 在所有 python3 提交中击败了20.80%的用户
'''
def threeSum3(self, nums: list) -> list:
lib = {}
ans = []
for num in nums: #以元素值为键,出现次数为值
lib[num] = lib.get(num, 0) + 1
if lib.get(0, 0) >= 3: #元素0出现了3次或更多
ans.append([0, 0, 0])
neg = sorted([x for x in lib if x < 0])
pos = sorted([x for x in lib if x >= 0])
for i in neg: #向右扫描负数
for j in pos[::-1]: #向左扫描正数
k = 0 - i - j
if k in lib:
if k in (i, j) and lib[k] >= 2:
ans.append([i, j, k])
elif k > i and k < j: #为避免重复,k必须在[i,j]范围内
ans.append([i, j, k])
return ans
#网友作品
def threeSum4(self, nums: [int]) -> [[int]]:
nums.sort()
res, k = [], 0
for k in range(len(nums) - 2):
if nums[k] > 0: break # 1. because of j > i > k.
if k > 0 and nums[k] == nums[k - 1]: continue # 2. skip the same `nums[k]`.
i, j = k + 1, len(nums) - 1
while i < j: # 3. double pointer
s = nums[k] + nums[i] + nums[j]
if s < 0:
i += 1
while i < j and nums[i] == nums[i - 1]: i += 1
elif s > 0:
j -= 1
while i < j and nums[j] == nums[j + 1]: j -= 1
else:
res.append([nums[k], nums[i], nums[j]])
i += 1
j -= 1
while i < j and nums[i] == nums[i - 1]: i += 1
while i < j and nums[j] == nums[j + 1]: j -= 1
return res
x = Solution()
a = [-4,-2,1,-5,-4,-4,4,-2,0,4,0,-2,3,1,-5,0]
print(x.threeSum2(a))
print(x.threeSum3(a))
print(x.threeSum(a))