上周末参加了leetcode的考试, 做出两道题。不得不接受自己的愚笨。
第三题在看了排名前两名java的答案和另一个python做的答案之后,除了感叹人家算法的精妙,也只能暗下决心,要花更多的时间来刷题!
https://leetcode.com/contest/weekly-contest-106/problems/3sum-with-multiplicity/
这里整理一下上周末的第三道题。
第三道题是这样的。
Given an integer array A, and an integer target, return the number of tuples i, j, k such that i < j < k and A[i] + A[j] + A[k] == target.
As the answer can be very large, return it modulo 10^9 + 7.
Example 1:
Input: A = [1,1,2,2,3,3,4,4,5,5], target = 8
Output: 20
Explanation:
Enumerating by the values (A[i], A[j], A[k]):
(1, 2, 5) occurs 8 times;
(1, 3, 4) occurs 8 times;
(2, 2, 4) occurs 2 times;
(2, 3, 3) occurs 2 times.
Example 2:
Input: A = [1,1,2,2,2,2], target = 5
Output: 12
Explanation:
A[i] = 1, A[j] = A[k] = 2 occurs 12 times:
We choose one 1 from [1,1] in 2 ways,
and two 2s from [2,2,2,2] in 6 ways.
Note:
3 <= A.length <= 3000
0 <= A[i] <= 100
0 <= target <= 300
简单的描述一下,就是给你一个数组,你找到数组里面所有的可能的按顺序的三个数a, b, c.使得他们加起来的值等于target.
这里先复盘一下我当时看到题时候是怎么想的。
我用的方法是用两个指针,分别从前后部分向中间走,target 减去两个指针就是第三个值。
但是我这里会出现一个问题。就是出现漏的情况。 我和小伙伴采取了一个稍微笨的方法。就是穷举所有可能,三遍for循环。然后拿到所有的可能之后进行去重。去重之后,再分别的计算每一个满足条件的组合。 这种情况我们最后做出来了,但是时间复杂度超了。。。
这里对之前思考的代码整理一下
from collections import Counter
class Solution(object):
# def threeSumMulti(self, A, target):
# """
# :type A: List[int]
# :type target: int
# :rtype: int
# """
def jiecheng(self, n):
ret = 1
while n:
ret *=n
n -=1
return ret
def cmn(self, m, n):
return self.jiecheng(m)/self.jiecheng(n)/self.jiecheng(m-n)
def threeSumMulti(self, l, target):
leng = len(l)
if leng <= 3:
if sum(l) != target:
return 0
else:
return 1
if max(l) * 3 < target or min(l) * 3 > target:
return 0
c_l = Counter(l)
if c_l.keys()[0] * 3 == target:
m = c_l.keys()[0]
rets = [[m,m,m]]
else:
l.sort()
res_list = []
for max_pos, max_value in enumerate(l):
mid_pos = max_pos + 1
min_pos = len(l) -1
while mid_pos < min_pos:
v = l[max_pos] + l[mid_pos] + l[min_pos]
if v == target:
res_list.append([l[max_pos], l[mid_pos], l[min_pos]])
mid_pos +=1
elif v < target:
mid_pos +=1
else:
min_pos -=1
ret_opreation = [sorted(i) for i in res_list]
rets = []
ret = [rets.append(i) for i in ret_opreation if not i in rets]
print rets
ret_sum = 0
a = Counter(l)
for i in rets:
ones = 1
i = Counter(i)
i_key = i.keys()
i_value = i.values()
a_value = [a[j] for j in i_key]
for h, r in zip(i_value, a_value):
# print(h, r), "***"
if r < h:
raise Error
ones *= self.cmn(r, h)
ret_sum += ones
return ret_sum % (10**9 + 7)
leetcode做的最快的那个,用了一个计数排序的方法就高效的解决了这个问题。他的代码我们第一次复现为python的时候,用时大概92ms, 我自己看完后重写了一个用时大概200多秒。
之所以他会快,是因为人家好好读题了。题就在Note部分。 A的长度最大是3000. 但是A里面的每个值却不超过100. 这个时候用计数排序就会非常快。
大概的思路是先用一个数组/字典a对数组A里面出现的次数进行计数。然后a里面对应下标的位置里面存储的是当前数出现的次数。这样遍历两次100,第一次是第一个值,从0开始的,第二次就是第二个值,只要与第一次不重复即可。而第三个值就是target 减去第一个值再减去第二个值。
然后只要满足都小于等于100. 就可以分情况讨论了。代码如下:
class Solution:
def threeSumMulti(self, A, target):
"""
:type A: List[int]
:type target: int
:rtype: int
"""
a = [0 for i in range(101)]
for i in A:
a[i] += 1
ret = 0
for i in range(101):
for j in range(101):
n = target - i - j
if i <= j and n <= 100:
if i < j < n:
ret += a[i] * a[j] * a[n]
elif i == n and n < j:
ret += a[j] * a[i] * (a[i]-1) // 2
elif n == j and i < n:
ret += a[j] * a[i] * (a[j]-1) // 2
elif n == j == i:
ret += a[i] * (a[i]-1) * (a[i]-2) // 6
return ret % (10 ** 9 + 7)
另外一种特别依赖python的实现方式。
from collections import defaultdict
class Solution:
def threeSumMulti(self, A, target):
"""
:type A: List[int]
:type target: int
:rtype: int
"""
a1 = defaultdict(int)
a2 = defaultdict(int)
a3 = defaultdict(int)
for i in A:
for m, n in a2.items():
a3[m + i] = (a3[m + i] + n) % (10**9 + 7)
for m, n in a1.items():
a2[m + i] = (a2[m + i] + n) % (10**9 + 7)
a1[i] += 1
return a3[target] % (10**9 + 7)
这种方式的巧妙之处在于它就像一个沙漏一样,一层一层的漏下来,最终流到最后一个沙漏的地方就是我们想要的结果集合。因为把所有的情况都给滤了一遍,所以它不会存在漏的情况。