Given a list of words
, a list of single letters
(might be repeating), and the 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. The 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.
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 lowercase English letters.A brute force approach would involve generating all possible subsets of the words
array and then, for each subset, checking if the letters required to form those words are available in the letters
array. If they are, we calculate the score for that subset and update the maximum score found so far.
This approach has exponential time complexity and is highly inefficient due to the generation of all subsets.
We can use dynamic programming combined with bit masking to efficiently solve this problem. The idea is to represent each subset of words with a bit mask. For example, if we have 4 words, the mask 0101
would mean that we are considering words at index 0 and 2 in our current subset.
Here are the steps:
letters
. This will help us quickly determine if we have enough letters to form a word.0
to 2^n - 1
, where n
is the number of words. Each value in this loop represents a bit mask indicating which words are included in the current subset.letters
.words
array: Return 0.letters
array: Return 0.letters
array: Skip the word.class Solution {
public int maxScoreWords(String[] words, char[] letters, int[] score) {
int[] letterCounts = new int[26];
for (char letter : letters) {
letterCounts[letter - 'a']++;
}
return solve(words, letterCounts, score, 0);
}
private int solve(String[] words, int[] letterCounts, int[] score, int index) {
if (index == words.length) {
return 0;
}
// Option 1: Skip the current word
int skip = solve(words, letterCounts, score, index + 1);
// Option 2: Include the current word
int currentScore = 0;
int[] newLetterCounts = letterCounts.clone();
boolean canInclude = true;
for (char c : words[index].toCharArray()) {
int charIndex = c - 'a';
if (newLetterCounts[charIndex] == 0) {
canInclude = false;
break;
}
newLetterCounts[charIndex]--;
currentScore += score[charIndex];
}
int include = 0;
if (canInclude) {
include = currentScore + solve(words, newLetterCounts, score, index + 1);
}
return Math.max(skip, include);
}
}
n
is the number of words and m
is the maximum length of a word. We iterate through all possible subsets (2n), and for each subset, we iterate through the letters of the selected words (O(m) in the worst case for each subset). However, since word length is constrained by 15, m
is relatively small.letterCounts
array has a fixed size of 26. The recursion depth can be at most n
, but the dominant factor is the fixed-size letterCounts
and the call stack which is relatively small.