给定一个整数数组 A,以及一个整数 target 作为目标值,返回满足 i < j < k 且 A[i] + A[j] + A[k] == target 的元组 i, j, k 的数量。
由于结果会非常大,请返回 结果除以 10^9 + 7 的余数。
示例 1:
输入:A = [1,1,2,2,3,3,4,4,5,5], target = 8
输出:20
解释:
按值枚举(A[i],A[j],A[k]):
(1, 2, 5) 出现 8 次;
(1, 3, 4) 出现 8 次;
(2, 2, 4) 出现 2 次;
(2, 3, 3) 出现 2 次。
示例 2:
输入:A = [1,1,2,2,2,2], target = 5
输出:12
解释:
A[i] = 1,A[j] = A[k] = 2 出现 12 次:
我们从 [1,1] 中选择一个 1,有 2 种情况,
从 [2,2,2,2] 中选出两个 2,有 6 种情况。
提示:
- 3 <= A.length <= 3000
- 0 <= A[i] <= 100
- 0 <= target <= 300
题意分析:
在整数数组A中找三个数使三个数的和等于给定值,A中包含重复元素。
A的长度不超过3000,理论上n^2的复杂度是可以被接受的。
思路分析:
我们很轻易的就能想到固定住一个数a[i],然后在剩下的元素找和为target - a[i]的结果,就把问题转换成了一个twoSum问题。
思路没有问题,但是在这里如何去写twoSum()呢
通常我们有两种方法,一种是使用空间vis去保存已经遍历过的数
,然后观察target-A[i]是否存在于vis中
.1
2
3
4
5
6
7
8
9
10
11# O(n) time, O(n) space
import collections
def twoSum(A,target):
if target < 0: return 0
d = collections.Counter()
res = 0
for val in A:
if target - val in d:
res += d[target-val]
d[val] += 1
return res
另一种是先排序,然后使用双指针法遍历
,但是因为存在重复数字,所以写起来并不是那么简单。
当然实际排序在3Sum函数中已经排好序了。这里函数是对已经排好序的数组使用双指针法的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29# O(n) time, O(1) space, 2608ms
def twoSum(A,target):
if target < 0: return 0
i,j = 0,len(A)-1
ans = 0
while i < j:
while i < j and A[i] + A[j] < target: i += 1
while i < j and A[i] + A[j] > target: j -= 1
# 当相等之后开始分析重复数字
if A[i] + A[j] == target:
if A[i] == A[j]:
ans += (j-i+1)*(j-i) // 2
break
else:
l,r = 1,1
while i+1 < j and A[i+1] == A[i]: l += 1;i += 1
while i < j-1 and A[j-1] == A[j]: r += 1;j -= 1
ans += l * r
i += 1
j -= 1
return ans
def threeSumMulti(A, target):
res = 0
mod = 10**9+7
A.sort()
for i in range(len(A)):
res += twoSum(A[i+1:], target-A[i])
return res % mod
第一种方法简单,但是却会TLE,首先Counter()函数的构造是很花时间的;就算把Counter()函数换成了d = [0] * 100
(因为A[i]的取值不超过100),还是TLE。
因为我自己代码逻辑的问题,每次twoSum都用重新开空间,确实会有很大的重复计算,但是我想每次重开的空间d,其实数量并不大,也就不到100,A的长度不超过3000,总空间也不会超过300000啊,感觉应该是能AC的。
然后我写了个O(1) space的解法,最后勉强能通过,我猜测可能是python本身语言特性的原因导致tle。
当然为了验证这个猜测,我也仿照discuss里面别人c++的思路来写python代码,但只要时间复杂度为O(n^2)并涉及到空间的都会TLE。
唯独这一种解法,在使用单纯的数组而非Counter()函数后,是勉强可以通过的,运行时间和我的解法差不多。(所以我脑子还是不够灵性,像这样计算前面的twoSum就不用每次都重开数组了)
1 | # O(n^2) time, O(300) space 2744ms |