[LeetCode] 1255. Maximum Score Words Formed by Letters 得分最高的單詞集合



Given a list of words, list of  single letters (might be repeating) and score of every character.

Return the maximum score of any valid set of words formed by using the given letters (words[i] cannot be used two or more times).

It is not necessary to use all characters in letters and each letter can only be used once. Score of letters 'a''b''c', ... ,'z' is given by score[0]score[1], ... , score[25] respectively.

Example 1:

Input: words = ["dog","cat","dad","good"], letters = ["a","a","c","d","d","d","g","o","o"], score = [1,0,9,5,0,0,3,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0]
Output: 23
Explanation:
Score  a=1, c=9, d=5, g=3, o=2
Given letters, we can form the words "dad" (5+1+5) and "good" (3+2+2+5) with a score of 23.
Words "dad" and "dog" only get a score of 21.

Example 2:

Input: words = ["xxxz","ax","bx","cx"], letters = ["z","a","b","c","x","x","x"], score = [4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,10]
Output: 27
Explanation:
Score  a=4, b=4, c=4, x=5, z=10
Given letters, we can form the words "ax" (4+5), "bx" (4+5) and "cx" (4+5) with a score of 27.
Word "xxxz" only get a score of 25.

Example 3:

Input: words = ["leetcode"], letters = ["l","e","t","c","o","d"], score = [0,0,1,1,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0]
Output: 0
Explanation:
Letter "e" can only be used once.

Constraints:

  • 1 <= words.length <= 14
  • 1 <= words[i].length <= 15
  • 1 <= letters.length <= 100
  • letters[i].length == 1
  • score.length == 26
  • 0 <= score[i] <= 10
  • words[i]letters[i] contains only lower case English letters.

這道題說是給了一個單詞數組 words,還有個字母數組 letters,以及每個字母的分數數組 score,現在讓用 letters 中的字母去組成 words 中的單詞,每個字母只能用一次,不必使用所有的字母,問可以得到的最大分數是多少,每個拼出的單詞的分數是其所有的字母的分數之和。題目中限定了只有小寫字母,即 26 個,所以 score 數組的大小也是 26 個,可以直接根據字母取其分數值。對於給定的字母數組 letters,里面可能會存在大量的重復字母,為了便於使用,需要統計每個字母的個數,可以用一個大小為 26 的 cnt 數組來統計。由於不知道給定的字母能拼出多少個單詞,但最終能拼出的單詞集一定是給定單詞集的子集,需要注意的是也不是拼出的單詞越多越好,而是需要最終的得分最大,所以只能盡可能的去計算每一種情況,從而找到得分最大的情況。

跟之前那道 Subsets 有點類似,不過更加復雜一些。這里使用回溯 Backtracking 的方法來做,遞歸函數需要4個參數,原單詞數組 words,統計字母個數數組 cnts,字母分數數組 score,還有當前遍歷的位置 idx。從 idx 的位置開始往后遍歷,對於當前遍歷到的單詞,遍歷其每一個字母,並且將 cnt 對應的字母個數減1,當小於0了的話,說明此時無法組成當前單詞,標記 isValid 為 false,同時將當前單詞的總得分存入變量 sum 中。處理完當前單詞后,若 isValid 為 true,說明字母是夠用的,則對下一個位置的單詞調用遞歸,將返回值加到 sum 中,這樣 sum 就是合法的情況,用其來更新結果 res。之后要記得恢復狀態,將當前單詞的字母統計個數加回來,參見代碼如下:


解法一:

class Solution {
public:
    int maxScoreWords(vector<string>& words, vector<char>& letters, vector<int>& score) {
        vector<int> cnt(26);
        for (char c : letters) ++cnt[c - 'a'];
        return dfs(words, cnt, score, 0);
    }
    int dfs(vector<string>& words, vector<int>& cnt, vector<int>& score, int idx) {
        int res = 0;
        for (int i = idx; i < words.size(); ++i) {
            int sum = 0;
            bool isValid = true;
            for (char c : words[i]) {
                if (--cnt[c - 'a'] < 0) {
                    isValid = false;
                }
                sum += score[c - 'a'];
            }
            if (isValid) {
                sum += dfs(words, cnt, score, i + 1);
                res = max(res, sum);
            }
            for (char c : words[i]) {
                ++cnt[c - 'a'];
            }
        }
        return res;
    }
};

我們還可以寫的更簡潔一些,在遞歸函數中少用一個 for 循環,開始直接判斷當前 idx 是否大於等於 words 的長度,是的話直接返回0。否則先跳過當單詞,直接對下個位置調用遞歸,得到返回值 skipGain,然后再來計算當前的得分 gain。這里為了避免回溯的恢復狀態步驟,直接新建了一個字母統計個數數組 cnt1,然后就是遍歷當前單詞的字母,減少對應字母的個數,若小於0了,isValid 標記為 false,將字母分數加到 gain 中。若最終 isValid 為 true,則分數為 gain 加上對下一個位置調用遞歸的返回的值(注意這次調用和開頭得到的 skipGain 是不同,因為傳入的 cnt 數組不同,這次調用已經減去當前單詞的字母個數),若 isValid 為 false,則分數為0。最終返回的分數還要跟 skipGain 相比,取較大值返回即可,參見代碼如下:


解法二:

class Solution {
public:
    int maxScoreWords(vector<string>& words, vector<char>& letters, vector<int>& score) {
        vector<int> cnt(26);
        for (char c : letters) ++cnt[c - 'a'];
        return dfs(words, cnt, score, 0);
    }
    int dfs(vector<string>& words, vector<int>& cnt, vector<int>& score, int idx) {
        if (idx >= words.size()) return 0;
        int skipGain = dfs(words, cnt, score, idx + 1), gain = 0;
        bool isValid = true;
        vector<int> cnt1 = cnt;
        for (char c : words[idx]) {
            if (--cnt1[c - 'a'] < 0) isValid = false;
            gain += score[c - 'a'];
        }
        return max(skipGain, isValid ? gain + dfs(words, cnt1, score, idx + 1) : 0);
    }
};

最后來看一種迭代的解法,這里是遍歷 words 的所有的子集,用了 bitmask 的方法,若 words 里有n個單詞,那么其子集的個數是 2^n,對應的正好是從 0 到 2^n-1 的二進制表示,0位表示不用對應位單詞,1表示選用當前單詞。比如 words 是 ["a", "b", "c"] 的話,那么 101 就表示子集是 ["a", "c"],這樣就可以遍歷所有的子集了。開始還是先統計 letters 中的字母個數,放入到 count 中,然后 mask 從0遍歷到 2^n-1,對於每個子集,復制一個 count 數組,然后遍歷n個位置,從0遍歷到 n-1,或者從 n-1 遍歷到0都可以,為的是找到二進制數對應的1位,通過 (mask >> i) & 1 來判斷,遍歷需要拼的單詞的字母,減去對應的字母個數,若小於0了,標記 isValid,並 break 掉。每遍歷完子集中的一個單詞,都檢查一下 isValid,若為0就 break 掉循環,最終該子集中的單詞遍歷完了之后,若 isValid 為1,用 sum 來更新結果 res 即可,參見代碼如下:


解法三:

class Solution {
public:
    int maxScoreWords(vector<string>& words, vector<char>& letters, vector<int>& score) {
        int res = 0, n = words.size(), m = 1 << n;
        vector<int> count(26);
        for (char c : letters) ++count[c - 'a'];
        for (int mask = 0; mask < m; ++mask) {
            int sum = 0, isValid = 1;
            vector<int> cnt = count;
            for (int i = n - 1; i >= 0; --i) {
                if ((mask >> i) & 1) {
                    for (char c : words[i]) {
                        if (--cnt[c - 'a'] < 0) {
                            isValid = 0;
                            break;
                        }
                        sum += score[c - 'a'];
                    }
                }
                if (!isValid) break;
            }
            if (isValid) res = max(res, sum);
        }
        return res;
    }
};

Github 同步地址:

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


類似題目:

Subsets


參考資料:

https://leetcode.com/problems/maximum-score-words-formed-by-letters/

https://leetcode.com/problems/maximum-score-words-formed-by-letters/discuss/426045/C%2B%2B-DFS-(optional-memo)

https://leetcode.com/problems/maximum-score-words-formed-by-letters/discuss/505887/C%2B%2B-Memory-efficient-simple-bitmasking-solution

https://leetcode.com/problems/maximum-score-words-formed-by-letters/discuss/425129/java-backtrack-similar-to-78.-Subsets-1ms-beats-100


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


免責聲明!

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



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