1. 程式人生 > >leetcode threeSumMulti 的三種python解法(後兩種特別好)

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)

這種方式的巧妙之處在於它就像一個沙漏一樣,一層一層的漏下來,最終流到最後一個沙漏的地方就是我們想要的結果集合。因為把所有的情況都給濾了一遍,所以它不會存在漏的情況。