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
類似題目:
參考資料:
https://leetcode.com/problems/maximum-score-words-formed-by-letters/