Taro Logo

Maximum Score Words Formed by Letters

Hard
Meta logo
Meta
Topics:
ArraysStringsDynamic ProgrammingBit Manipulation

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.

Solution


Naive Solution (Brute Force)

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.

Optimal Solution (Dynamic Programming with Bit Masking)

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:

  1. Count Letter Frequencies: Create a frequency map for the available letters. This will help us quickly determine if we have enough letters to form a word.
  2. Iterate through all possible subsets: Use a loop that goes from 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.
  3. Check Letter Availability: For each subset, iterate through the words indicated by the bit mask. Check if the required letters for each word are available, given the remaining letter counts from the input letters.
  4. Calculate Score: If all letters are available, calculate the score for the current subset of words.
  5. Update Maximum Score: Keep track of the maximum score found so far and update it as needed.

Edge Cases

  • Empty words array: Return 0.
  • Empty letters array: Return 0.
  • A word requires more occurrences of a character than available in the letters array: Skip the word.

Code Example (Java)

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

Time and Space Complexity

  • Time Complexity: O(2n * m), where 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.
  • Space Complexity: O(1). The 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.