[LeetCode] 1255. Maximum Score Words Formed by Letters

Posted by linfidel on Sun, 21 Nov 2021 21:23:46 +0100


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.

This question gives a word array words, a letter array letters, and a score array for each letter. Now let's use the letters in letters to form words in words. Each letter can only be used once without using all the letters. What's the maximum score you can get, The score of each spelled word is the sum of the scores of all its letters. There are only 26 lowercase letters in the title, so the size of the score array is also 26. You can take its score value directly according to the letters. For a given letter array letters, there may be a large number of duplicate letters. For ease of use, it is necessary to count the number of each letter, which can be counted by a cnt array with a size of 26. Because I don't know how many words a given letter can spell, but the final word set that can spell must be a subset of the given word set. It should be noted that the more words you spell, the better, but the final score is the largest. Therefore, I can only calculate each case as much as possible to find the case with the largest score.

Same as before Subsets A little similar, but more complex. The method of Backtracking is used here. The recursive function requires four parameters, the original word array words, the statistical letter number array cnts, the letter fraction array score, and the current traversal position idx. Starting from the position of idx, traverse each letter of the currently traversed word, and reduce the number of letters corresponding to cnt by 1. If it is less than 0, it means that the current word cannot be formed at this time, mark isValid as false, and store the total score of the current word in the variable sum. After processing the current word, if isValid is true, it means that the letters are enough, then recursion is called for the word at the next position and the return value is added to sum. In this way, sum is legal and used to update the result res. After that, remember to restore the status and add back the statistical number of letters of the current word. See the code as follows:


Solution 1:

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;
    }
};

We can also write more succinctly. We can use less a for loop in the recursive function to directly judge whether the current idx is greater than or equal to the length of words. If so, we can directly return 0. Otherwise, skip the current word and call recursion directly to the next position to get the return value skipGain, and then calculate the current score gain. In order to avoid the recovery step of backtracking, an array cnt1 of letters statistics is directly created, and then the letters of the current word are traversed to reduce the number of corresponding letters. If it is less than 0, isValid is marked as false, and the letter score is added to gain. If the final isValid is true, the score is gain plus the returned value of the recursive call to the next position (note that this call is different from the skipGain obtained at the beginning, because the incoming CNT array is different, and the number of letters of the current word has been subtracted in this call). If isValid is false, the score is 0. The final returned score should be compared with skipGain. Take a larger value to return. See the code below:


Solution 2:

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);
    }
};

Finally, let's look at an iterative solution. Here, we traverse all subsets of words, using the bitmask method. If there are n words in words, the number of subsets is 2^n, which corresponds to the binary representation from 0 to 2^n-1. Bit 0 means that there is no corresponding word, and bit 1 means that the current word is selected. For example, if words is ["a", "b", "c"], then 101 means that the subset is ["a", "c"], so you can traverse all subsets. First, count the number of letters in letters and put them into count. Then, mask traverses from 0 to 2^n-1. For each subset, copy a count array, and then traverse n positions, from 0 to n-1, or from n-1 to 0. In order to find the 1 bit corresponding to the binary number, judge through (mask > > I) & 1, and traverse the letters of the words to be spelled, Subtract the number of corresponding letters. If it is less than 0, mark isValid and break. After traversing a word in the subset, check isValid. If it is 0, break the loop. Finally, after traversing the words in the subset, if isValid is 1, use sum to update the result res. see the code below:


Solution 3:

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 sync address:

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


Similar topics:

Subsets


reference material:

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 topic explanation summary (under continuous update...)

Topics: leetcode