1. 程式人生 > >Leetcode 943:最短超級串(超詳細的解法!!!)

Leetcode 943:最短超級串(超詳細的解法!!!)

給定一個字串陣列 A,找到以 A 中每個字串作為子字串的最短字串。

我們可以假設 A 中沒有字串是 A 中另一個字串的子字串。

示例 1:

輸入:["alex","loves","leetcode"]
輸出:"alexlovesleetcode"
解釋:"alex","loves","leetcode" 的所有排列都會被接受。

示例 2:

輸入:["catg","ctaagt","gcta","ttca","atgcatc"]
輸出:"gctaagttcatgcatc"

提示:

  1. 1 <= A.length <= 12
  2. 1 <= A[i].length <= 20

解題思路

這個問題非常簡單,前提是你非常熟悉Travelling Salesman Problem(旅行商問題),這個問題其實就是一個變相的旅行商問題。我們怎麼把它轉化為旅行商問題呢?我們首先要定義一種距離,用來描述兩個字串之間的差別。例如

gcta -> ctaagt

對於上面的示例,我們首先計算字串gcta的結尾與ctaagt開頭有多少元素是一樣的,我們通過計算髮現有三個。此時,我們就定義這兩者之間的距離是len(atgcatc)-3,也就是如果ctaagt加到gcta後我們要增加的長度,我們遍歷所有字串,然後我們求出分別以此字串出發最後回到此字串的最短距離,那麼這個最短的距離一定就是我們所需要的結果。

我們首先計算不同字串之間的距離,新增到一個graph中去。然後通過tsp尋找以哪個字串開始最後的結果是最短的,並且在此過程中我們要記錄遍歷字串的順序(我們通過path)。然後我們通過這個找到的頭字串,並且根據我們之前記錄的path將所有字串都合在一起就是我們最後的結果。

class Solution:
    def shortestSuperstring(self, A):
        """
        :type A: List[str]
        :rtype: str
        """
        A_len = len(A)
        graph =
[[0]*A_len for _ in range(A_len)] mem = [[-1]*A_len for _ in range(1 << A_len)] path = [[0]*A_len for _ in range(1 << A_len)] visited_all = (1 << A_len) - 1 def _calc(a, b): for i in range(1, len(a)): if len(a) - i <= len(b) and a[i:] == b[:len(a)-i]: return len(b) - len(a) + i return len(b) for i in range(A_len): for j in range(A_len): if i == j: continue graph[i][j] = _calc(A[i], A[j]) def _tsp(mask, pos): if mask == visited_all: return 0 if mem[mask][pos] != -1: return mem[mask][pos] result = float("inf") for i in range(A_len): if (1 << i) & mask: continue newResult = graph[pos][i] + _tsp(mask | (1 << i), i) if result > newResult: result = newResult path[mask][pos] = i mem[mask][pos] = result return result ret, best = float("inf"), -1 for i in range(A_len): cur = len(A[i]) + _tsp(1 << i, i) if ret > cur: ret = cur best = i mask = 1 << best result_str = A[best] while mask != visited_all: j = path[mask][best] result_str += A[j][len(A[j]) - graph[best][j]:] mask |= (1 << j) best = j return result_str

上面這個演算法的思路非常清晰,但是顯然不是最好的。還有一個比較好的思路就是貪心策略,我們首先從全部的字串中找兩兩之間重疊最多的兩個字串,這兩個字串一定在最後的結果中。例如

"catg","ctaagt","gcta","ttca","atgcatc"

上述的字串中重複最多的是gctactaagt,所以我們可以將這兩個元素合併,結果就是gctaagt,它一定在最後的結果中。現在我們就將這個合併過的字串新增到我們的查詢字串中,並且我們要從gctactaagt中去掉ctaagt

"catg","gctaagt","ttca","atgcatc"

現在我們繼續合併,也就是先找重複字元最多的兩個字串。此時我們發現catgatgcatc重複最多,我們就將他倆合併,然後刪除atgatc

"gctaagt","ttca","catgcatc"

以此類推。

class Solution:
    def shortestSuperstring(self, A):
        """
        :type A: List[str]
        :rtype: str
        """
        def findOverlappingPair(s1, s2):
            max_overlap_len = float("-inf") 
            n = min(len(s1), len(s2))
            res = ''
            for i in range(1, n+1):
                if s1.endswith(s2[:i]):
                    if max_overlap_len < i:
                        max_overlap_len = i
                        res = s1 + s2[i:]
            for i in range(1, n+1):
                if s2.endswith(s1[:i]):
                    if max_overlap_len < i:
                        max_overlap_len = i
                        res = s2 + s1[i:]

            return max_overlap_len, res
        
        A_len = len(A)
        
        while A_len != 1:
            p, q = -1, -1
            res = ''
            max_val = float("-inf") 
            for i in range(A_len):
                for j in range(i+1, A_len):
                    r, tmp_res = findOverlappingPair(A[i], A[j])
                    if max_val < r:
                        max_val = r
                        res = tmp_res
                        p = j
                        q = i
            A_len -= 1
            if max_val == float("-inf"): 
                A[0] = A[0] + A[A_len]
            else:
                A[p] = res
                A[q] = A[A_len]
            
        return A[0]

reference:

如有問題,希望大家指出!!!