Leetcode 943:最短超級串(超詳細的解法!!!)
給定一個字串陣列 A
,找到以 A
中每個字串作為子字串的最短字串。
我們可以假設 A
中沒有字串是 A
中另一個字串的子字串。
示例 1:
輸入:["alex","loves","leetcode"]
輸出:"alexlovesleetcode"
解釋:"alex","loves","leetcode" 的所有排列都會被接受。
示例 2:
輸入:["catg","ctaagt","gcta","ttca","atgcatc"]
輸出:"gctaagttcatgcatc"
提示:
1 <= A.length <= 12
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"
上述的字串中重複最多的是gcta
和ctaagt
,所以我們可以將這兩個元素合併,結果就是gctaagt
,它一定在最後的結果中。現在我們就將這個合併過的字串新增到我們的查詢字串中,並且我們要從gcta
和ctaagt
中去掉ctaagt
。
"catg","gctaagt","ttca","atgcatc"
現在我們繼續合併,也就是先找重複字元最多的兩個字串。此時我們發現catg
和atgcatc
重複最多,我們就將他倆合併,然後刪除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:
如有問題,希望大家指出!!!