943 Find the Shortest Superstring 最短超級串
Description:
Given an array of strings words, return the smallest string that contains each string in words as a substring. If there are multiple valid strings of the smallest length, return any of them.
You may assume that no string in words is a substring of another string in words.
Example:
Example 1:
Input: words = ["alex","loves","leetcode"]
Output: "alexlovesleetcode"
Explanation: All permutations of "alex","loves","leetcode" would also be accepted.
Example 2:
Input: words = ["catg","ctaagt","gcta","ttca","atgcatc"]
Output: "gctaagttcatgcatc"
Constraints:
1 <= words.length <= 12
1 <= words[i].length <= 20
words[i] consists of lowercase English letters.
All the strings of words are unique.
題目描述:
給定一個字符串數組 words,找到以 words 中每個字符串作爲子字符串的最短字符串。如果有多個有效最短字符串滿足題目條件,返回其中 任意一個 即可。
我們可以假設 words 中沒有字符串是 words 中另一個字符串的子字符串。
示例 :
示例 1:
輸入:words = ["alex","loves","leetcode"]
輸出:"alexlovesleetcode"
解釋:"alex","loves","leetcode" 的所有排列都會被接受。
示例 2:
輸入:words = ["catg","ctaagt","gcta","ttca","atgcatc"]
輸出:"gctaagttcatgcatc"
提示:
1 <= words.length <= 12
1 <= words[i].length <= 20
words[i] 由小寫英文字母組成
words 中的所有字符串 互不相同
思路:
動態規劃狀態壓縮
用 state 表示使用的字符串, 位對應 1 表示使用了 words[i]
設 dp[state][i] 表示第 i 個單詞放在首位的單詞長度
比如 dp[0x1111][0] 表示 words[0] 放在首位, 使用了 words[:4] 的所有單詞
那麼 dp[0x1111][0] = len(words[0] + min(dp[0x1110][1] - overlap[0][1], dp[0x1110][2] - overlap[0][2], dp[0x1110][3] - overlap[0][3]
就是將 words[0] 放在首位, 那麼找到 words[1], words[2], words[3] 依次放在首位的最短單詞長度即可
overlap 爲預先計算的兩個單詞的前後綴的最大長度
另外因爲要輸出單詞所以還要用 pre 數組記錄下轉移的位置
時間複雜度爲 O(n ^ 2 * 2 ^ n), 空間複雜度爲 O(n * 2 ^ n), n 爲 words 的長度
代碼:
C++:
class Solution {
public:
string shortestSuperstring(vector<string>& words)
{
const int INF = 1e9, n = words.size();
int min_len = INF, start = -1, mask = (1 << n) - 1, cur = 0;
vector<vector<int>> overlaps(n, vector<int>(n)), dp(1 << n, vector<int>(n, INF)), pre(1 << n, vector<int>(n));
for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) if (i != j) for (int k = min(words[i].size(), words[j].size()); k > -1; k--) if (words[i].substr((int)words[i].size() - k) == words[j].substr(0, k))
{
overlaps[i][j] = k;
break;
}
for (int state = 1; state <= mask; state++) for (int i = 0; i < n; i++)
{
if ((state >> i) & 1)
{
if (!(state & (state - 1)))
{
dp[state][i] = words[i].size();
pre[state][i] = i;
}
else
{
for (int j = 0; j < n; j++) if (i != j) if ((state >> j) & 1)
{
cur = dp[state ^ (1 << i)][j] + words[i].size() - overlaps[j][i];
if (cur < dp[state][i])
{
dp[state][i] = cur;
pre[state][i] = j;
}
}
}
}
}
for (int i = 0; i < n; i++) if (dp[mask][i] < min_len)
{
min_len = dp[mask][i];
start = i;
}
vector<int> path{ start };
int state = mask, pre_id = start;
for (int i = 0; i < n - 1; i++)
{
int cur_id = pre[state][pre_id];
path.emplace_back(cur_id);
state ^= (1 << pre_id);
pre_id = cur_id;
}
reverse(path.begin(), path.end());
string result = words[path.front()];
for (int k = 1; k < n; k++) result += words[path[k]].substr(overlaps[path[k - 1]][path[k]]);
return result;
}
};
Java:
class Solution {
public String shortestSuperstring(String[] words) {
final int INF = 1_000_000_000;
int n = words.length, minLen = INF, start = -1, mask = (1 << n) - 1, overlaps[][] = new int[n][n], dp[][] = new int[1 << n][n], pre[][] = new int[1 << n][n], cur = 0;
for (int i = 0; i < (1 << n); i++) Arrays.fill(dp[i], INF);
for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) if (i != j) for (int k = Math.min(words[i].length(), words[j].length()); k > -1; k--) if (words[i].endsWith(words[j].substring(0, k))) {
overlaps[i][j] = k;
break;
}
for (int state = 1; state <= mask; state++) for (int i = 0; i < n; i++) {
if (((state >> i) & 1) != 0) {
if ((state & (state - 1)) == 0) {
dp[state][i] = words[i].length();
pre[state][i] = i;
} else {
for (int j = 0; j < n; j++) if (i != j) if (((state >> j) & 1) != 0) {
cur = dp[state ^ (1 << i)][j] + words[i].length() - overlaps[j][i];
if (cur < dp[state][i]) {
dp[state][i] = cur;
pre[state][i] = j;
}
}
}
}
}
for (int i = 0; i < n; i++) if (dp[mask][i] < minLen) {
minLen = dp[mask][i];
start = i;
}
List<Integer> path = new ArrayList<>();
path.add(start);
int state = mask, preId = start;
for (int i = 0; i < n - 1; i++) {
int curId = pre[state][preId];
path.add(curId);
state ^= (1 << preId);
preId = curId;
}
Collections.reverse(path);
StringBuilder result = new StringBuilder(words[path.get(0)]);
for (int k = 1; k < n; k++) result.append(words[path.get(k)].substring(overlaps[path.get(k - 1)][path.get(k)]));
return result.toString();
}
}
Python:
class Solution:
def shortestSuperstring(self, words: List[str]) -> str:
INF, min_len, start, mask = 10 ** 9, 10 ** 9, -1, (1 << (n := len(words))) - 1
overlap, dp, pre = [[0] * n for _ in range(n)], [[INF] * n for _ in range((1 << n))], [[-1] * n for _ in range((1 << n))]
for i in range(n):
for j in range(n):
if i != j:
for k in range(min(len(words[i]), len(words[j])), 0, -1):
if words[i][-k:] == words[j][:k]:
overlap[i][j] = k
break
for state in range(1, 1 << n):
for i in range(n):
if (state >> i) & 1:
if bin(state).count('1') == 1:
dp[state][i], pre[state][i] = len(words[i]), i
else:
for j in range(n):
if i != j:
if (state >> j) & 1:
cur = dp[state ^ (1 << i)][j] + len(words[i]) - overlap[j][i]
if cur < dp[state][i]:
dp[state][i] = cur
pre[state][i] = j
for i in range(n):
if dp[mask][i] < min_len:
min_len = dp[mask][i]
start = i
path, state, pre_id = [start], mask, start
for _ in range(n - 1):
path.append((cur_id := pre[state][pre_id]))
state ^= (1 << pre_id)
pre_id = cur_id
path.reverse()
result = words[path[0]]
for k in range(1, n):
result += words[(i := path[k])][overlap[path[k - 1]][i]:]
return result