2 minute read

Problem Statement

1255

Intuition

To solve this problem, the goal is to maximize the score of words we can form using given letters. Each word has a score, and we can only use each letter a limited number of times. This problem can be approached using a combination of backtracking and bit manipulation to explore all possible combinations of words efficiently.

Approach

Word Scoring: First, we compute the score for each word individually if it can be formed with the given letters. This is done by checking if the word can be formed with the available letters and summing up the scores for the letters in the word.

Depth-First Search (DFS): We use DFS to explore all possible combinations of words that can be formed. For each combination, we check if the words can be formed with the available letters and update the maximum score if the current combination yields a higher score.

Backtracking: During the DFS, we use backtracking to explore both the scenarios of including a word in the current combination and not including it. This ensures that all possible combinations are considered.

Validation: We validate if a word can be formed with the remaining letters at each step of the DFS to ensure we are only considering valid combinations.

Complexity

  • Time complexity: O(2^n) where n is the number of words. This is because we are considering all subsets of the words

  • Space complexity: O(n) where n is the depth of the recursion stack used for DFS

Code

class Solution:
    def maxScoreWords(self, words: List[str], letters: List[str], score: List[int]) -> int:
        word_score = defaultdict(int)
        for word in words:
            curr_score = 0
            counter = Counter(letters)
            for c in word:
                counter[c] -= 1
                if counter[c] < 0:
                    curr_score = 0
                    break
                else:
                    index = ord(c) - ord('a')
                    curr_score += score[index]

            word_score[word] = curr_score

        self.res = 0
        counter = Counter(letters)

        def isValid(word):
            letter_freq = Counter(word)
            for c in word:
                if letter_freq[c] > counter[c] or c not in counter:
                    return False
            return True

        def dfs(i, curr, curr_score, word_list):
            if i == len(word_list):
                return
            if isValid(curr):
                self.res = max(self.res, curr_score)
            if i + 1 < len(word_list):
                dfs(i + 1, curr + word_list[i + 1], curr_score + word_score[word_list[i + 1]], word_list)
            dfs(i + 1, curr, curr_score, word_list)

        for i in range(len(words)):
            words[i], words[0] = words[0], words[i]
            dfs(0, words[0], word_score[words[0]], words)
            words[i], words[0] = words[0], words[i]


        return self.res


Editorial Solution

class Solution:
    def maxScoreWords(self, words: List[str], letters: List[str], score: List[int]) -> int:
        W = len(words)
        # Count how many times each letter occurs
        self.max_score = 0
        freq = [0 for i in range(26)]
        subset_letters = [0 for i in range(26)]
        for c in letters:
            freq[ord(c) - 97] += 1

        # Check if adding this word exceeds the frequency of any letter
        def is_valid_word(subset_letters):
            for c in range(26):
                if freq[c] < subset_letters[c]:
                    return False
            else:
                return True

        def check(w, words, score, subset_letters, total_score):
            if w == -1:
                # If all words have been iterated, check the score of this subset
                self.max_score = max(self.max_score, total_score)
                return
            # Not adding words[w] to the current subset
            check(w - 1, words, score, subset_letters, total_score)
            # Adding words[w] to the current subset
            L = len(words[w])
            for i in range(L):
                c = ord(words[w][i]) - 97
                subset_letters[c] += 1
                total_score += score[c]

            if is_valid_word(subset_letters):
                # Consider the next word if this subset is still valid
                check(w - 1, words, score, subset_letters, total_score)

            # Rollback effects of this word
            for i in range(L):
                c = ord(words[w][i]) - 97
                subset_letters[c] -= 1
                total_score -= score[c]

        check(W - 1, words, score, subset_letters, 0)
        # Return max_score as the result
        return self.max_score