[LeetCode] 1048. Longest String Chain 最長字符串鏈



Given a list of words, each word consists of English lowercase letters.

Let's say word1 is a predecessor of word2 if and only if we can add exactly one letter anywhere in word1 to make it equal to word2.  For example, "abc" is a predecessor of "abac".

A *word chain *is a sequence of words [word_1, word_2, ..., word_k] with k >= 1, where word_1 is a predecessor of word_2word_2 is a predecessor of word_3, and so on.

Return the longest possible length of a word chain with words chosen from the given list of words.

Example 1:

Input: words = ["a","b","ba","bca","bda","bdca"]
Output: 4
Explanation: One of the longest word chain is "a","ba","bda","bdca".

Example 2:

Input: words = ["xbc","pcxbcf","xb","cxbc","pcxbc"]
Output: 5

Constraints:

  • 1 <= words.length <= 1000
  • 1 <= words[i].length <= 16
  • words[i] only consists of English lowercase letters.

這道題給了一個單詞數組,定義了一種前任關系,說是假如在 word1 中任意位置加上一個字符,能變成 word2 的話,那么 word1 就是 word2 的前任,實際上 word1 就是 word2 的一個子序列。現在問在整個數組中最長的前任鏈有多長,暴力搜索的話會有很多種情況,會產生大量的重復計算,所以會超時。這種玩數組求極值的題十有八九都是用動態規划 Dynamic Programming 來做的,這道題其實跟之前那道 Longest Arithmetic Subsequence 求最長的等差數列的思路是很像的。首先來定義 dp 數組,這里用一個一維的數組就行了,其中 dp[i] 表示 [0, i] 區間的單詞的最長的前任鏈。下面來推導狀態轉移方程,對於當前位置的單詞,需要遍歷前面所有的單詞,這里需要先給單詞按長度排個序,因為只有長度小1的單詞才有可能是前任,所以只需要遍歷之前所有長度正好小1的單詞,若是前任關系,則用其 dp 值加1來更新當前 dp 值即可。判斷前任關系可以放到一個子數組中來做,其實就是檢測是否是子序列,沒啥太大的難度,參見代碼如下:


解法一:

class Solution {
public:
    int longestStrChain(vector<string>& words) {
        int n = words.size(), res = 1;
        sort(words.begin(), words.end(), [](string& a, string &b){
            return a.size() < b.size();
        });
        vector<int> dp(n, 1);
        for (int i = 1; i < n; ++i) {
            for (int j = i - 1; j >= 0; --j) {
                if (words[j].size() + 1 < words[i].size()) break;
                if (words[j].size() == words[i].size()) continue;
                if (helper(words[j], words[i])) {
                    dp[i] = max(dp[i], dp[j] + 1);
                    res = max(res, dp[i]);
                }
            }
        }
        return res;
    }
    bool helper(string word1, string word2) {
        int m = word1.size(), n = word2.size(), i = 0;
        for (int j = 0; j < n; ++j) {
            if (word2[j] == word1[i]) ++i;
        }
        return i == m;
    }
};

論壇上的高分解法在檢驗是否是前任時用了一種更好的方法,不是檢測子序列,而是將當前的單詞,按順序每次去掉一個字符,然后看剩下的字符串是否在之前出現過,是的話就說明有前任,用其 dp 值加1來更新當前 dp 值,這是一種更巧妙且簡便的方法。這里由於要快速判斷前任是否存在,所以不是用的 dp 數組,而是用了個 HashMap,對於每個遍歷到的單詞,按順序移除掉每個字符,若剩余的部分在 HashMap 中,則更新 dp 值和結果 res,參見代碼如下:


解法二:

class Solution {
public:
    int longestStrChain(vector<string>& words) {
        int n = words.size(), res = 1;
        sort(words.begin(), words.end(), [](string& a, string& b){ return a.size() < b.size(); });
        unordered_map<string, int> dp;
        for (string word : words) {
            dp[word] = 1;
            for (int i = 0; i < word.size(); ++i) {
                string pre = word.substr(0, i) + word.substr(i + 1);
                if (dp.count(pre)) {
                    dp[word] = max(dp[word], dp[pre] + 1);
                    res = max(res, dp[word]);
                }
            }
        }
        return res;
    }
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/1048


類似題目:

Longest Arithmetic Subsequence


參考資料:

https://leetcode.com/problems/longest-string-chain/

https://leetcode.com/problems/longest-string-chain/discuss/294890/JavaC%2B%2BPython-DP-Solution


LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM