leetcode threeSumMulti 的三種python解法(後兩種特別好)
上週末參加了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)
這種方式的巧妙之處在於它就像一個沙漏一樣,一層一層的漏下來,最終流到最後一個沙漏的地方就是我們想要的結果集合。因為把所有的情況都給濾了一遍,所以它不會存在漏的情況。