LeetCode #943 Find the Shortest Superstring 最短超級串 943 Find the Shortest Superstring 最短超級串

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章