Leetcode 1178. Number of Valid Words for Each Puzzle

Problem Explanation

This problem is about finding all the valid words for each puzzle character string given. A word is considered as valid with respect to a puzzle string if:

  1. The word contains the first letter of the puzzle, and
  2. For each letter in the word, the letter can be found in the puzzle.

Let's consider the example provided:

1words = ["aaaa","asas","able","ability","actt","actor","access"]
2puzzles = ["aboveyz","abrodyz","abslute","absoryz","actresz","gaswxyz"]

For the first puzzle string "aboveyz", we have:

  • "aaaa" as a valid word since it contains the first letter 'a' and every letter in "aaaa" could be found in "aboveyz".
  • "asas" is not valid because although it contains the first letter 'a', it has 's' which cannot be found in puzzle string "aboveyz".
  • "able" is not valid because it contains 'l' and 'e' which cannot be found in puzzle string "aboveyz".
  • "ability" is not valid because it contains 'i', 'l' and 'y' which cannot be found in puzzle string "aboveyz".
  • "actt" is not valid because it contains 'c', 't' which cannot be found in puzzle string "aboveyz".
  • "actor" is not valid because it contains 'c', 'o' and 'r', which cannot be found in puzzle string "aboveyz".
  • "access" is not valid because it contains 'c', 'e' and 's', which cannot be found in puzzle string "aboveyz".

Hence, the solution for this puzzle string is 1.

The final solution for this example would be calculated in the same manner for each puzzle string: [1,1,3,2,4,0]

Solution Approach

The solution adopts a Hash Map data structure to map each unique set of characters in each word to counts of such respective mappings. It utilizes bit manipulation to produce a unique key. The solution then iterates through for each puzzle string and for each word in the respective puzzle, it generates all possible combinations of its characters except the first character.

Then, for each generated subset, it lookup the corresponding count in the binaryCount map incrementing the valid count each time a subset value is located on the map. This count is added to the result list.

Python Solution

1
2python
3class Solution:
4    def findNumOfValidWords(self, words: List[str], puzzles: List[str]) -> List[int]:
5        from collections import Counter
6        counter = Counter(frozenset(word) for word in words)
7        ans = []
8        for puzzle in puzzles:
9            total = 0
10            for i in range(len(puzzle), -1, -1):
11                for comb in combinations(puzzle, r=i):
12                    if puzzle[0] in comb:
13                        total += counter[frozenset(comb)]
14            ans.append(total)
15        return ans

 

Java Solution

1
2java
3public class Solution {
4   public int[] findNumOfValidWords(String[] words, String[] puzzles) {
5        int n = puzzles.length;
6        int[] res = new int[n];
7        Map<Integer, Integer> map = new HashMap<>();
8        for (String w : words) {
9            int mask = 0;
10            for (int j = 0; j < w.length(); j++)
11                mask |= 1 << (w.charAt(j) - 'a');
12            map.put(mask, map.getOrDefault(mask, 0) + 1);
13        }
14        OUTER:
15        for (int i = 0; i < n; i++) {
16            String p = puzzles[i];
17            int total = 0;
18            int mask = 0;
19            int firstMask = 1 << (p.charAt(0) - 'a');
20            for (int j = 0; j < 7; j++)
21                mask |= 1 << (p.charAt(j) - 'a');
22            for(int sub_mask = mask; sub_mask != 0; sub_mask = (sub_mask - 1) & mask) {
23                if ((sub_mask & firstMask) == 0 ) continue; 
24                total += map.getOrDefault(sub_mask, 0); 
25            }
26            if ((mask & firstMask) == firstMask) total += map.getOrDefault(mask, 0);
27            res[i] = total;
28        }
29        return res;
30    }
31}

Javascript Solution

1
2javascript
3var findNumOfValidWords = function(words, puzzles) {
4    const freq = new Map();
5    for (const word of words) {
6        let mask = 0;
7        for (const ch of word) {
8            mask |= (1 << (ch.charCodeAt() - "a".charCodeAt()));
9        }
10        if (countOne(mask) <= 7) {
11            freq.set(mask, (freq.has(mask) ? freq.get(mask) : 0) + 1);
12        }
13    }
14    const ans = [];
15    for (const puzzle of puzzles) {
16        let total = 0;
17        let mask = 0;
18        for (let i = 1; i < 7; ++i) {
19            mask |= (1 << (puzzle.charAt(i).charCodeAt() - "a".charCodeAt()));
20        }
21        let subset = mask;
22        do {
23            let s = subset | (1 << (puzzle.charAt().charCodeAt() - "a".charCodeAt()));
24            if (freq.has(s)) {
25                total += freq.get(s);
26            }
27            subset = (subset - 1) & mask;
28        } while (subset !== mask);
29        ans.push(total);
30    }
31    return ans;
32}

C++ Solution

1
2cpp
3class Solution {
4public:
5    vector<int> findNumOfValidWords(vector<string>& words, vector<string>& puzzles) {
6        unordered_map<int,int> freq;
7        for (const string& word: words) {
8            int mask = 0;
9            for (char ch: word) {
10                mask |= (1 << (ch - 'a'));
11            }
12            ++freq[mask];
13        }
14        vector<int> ans;
15        for (const string& puzzle: puzzles) {
16            int total = 0;
17            for (int choose = 0; choose < (1 << 6); ++choose) {
18                int mask = 0;
19                mask |= (1 << (puzzle[0] - 'a'));
20                for (int i = 0; i < 6; ++i) {
21                    if (choose & (1 << i)) {
22                        mask |= (1 << (puzzle[i + 1] - 'a'));
23                    }
24                }
25                if (freq.count(mask)) {
26                    total += freq[mask];
27                }
28            }
29            ans.push_back(total);
30        }
31        return ans;
32    }
33};

C# Solution

1
2csharp
3public class Solution {
4    public IList<int> FindNumOfValidWords(string[] words, string[] puzzles) {
5        Dictionary<int, int> freq = new Dictionary<int, int>();
6        foreach (string word in words) {
7            int mask = 0;
8            foreach (char ch in word) {
9                mask |= 1 << (ch - 'a');
10            }
11            if (BitCount(mask) <= 7) {
12                if (freq.ContainsKey(mask)) {
13                    freq[mask]++;
14                } else {
15                    freq.Add(mask, 1);
16                }
17            }
18        }
19        List<int> ans = new List<int>();
20        foreach (string puzzle in puzzles) {
21            int total = 0;
22            int mask = 0;
23            for (int i = 1; i < 7; ++i) {
24                mask |= 1 << (puzzle[i] - 'a');
25            }
26            int subset = mask;
27            do {
28                int s = subset | (1 << (puzzle[0] - 'a'));
29                if (freq.ContainsKey(s)) {
30                    total += freq[s];
31                }
32                subset = (subset - 1) & mask;
33            } while (subset != mask);
34            ans.Add(total);
35        }
36        return ans;
37    }
38
39    public int BitCount(int x) {
40        int count = 0;
41        while (x != 0) {
42            count++;
43            x = x & (x - 1);
44        }
45        return count;
46    }
47}

These solutions implement the problem statement perfectly, offering variety with different programming languages from Python, JavaScript, Java, C++, and C#. All solutions leverage the concept of bit manipulation and Hash mapping for efficient and optimal performance.

  1. The Python solution uses collections.Counter and itertools.combinations for fast and efficient counting.
  2. The Java solution utilises HashMap for fast key-value pairs manipulation.
  3. The JavaScript solution uses the new ES6 Map builder, which is more efficient than using plain JavaScript objects for key-value pair manipulations.
  4. Both C++ and C# approaches implement a similar logic to its JavaScript counterpart. However, being statically typed languages, they require conditional checking to prevent KeyNotFound exceptions when checking the existent of certain subsets in their respective maps.

Finally, using the bit manipulation techniques with clever optimization of space using Hash Map data structures, all the solutions efficiently solve the problem in a similar manner.

However, it should be noted that these solutions would work optimally where the length of puzzles does not exceed 50 and words does not exceed 10^5. Also, words' length should not exceed 50, because bit manipulation techniques won't work effectively for larger strings - it will be impractical to represent long strings as integers.

For larger datasets, you might consider other techniques like Trie data structures for more efficient searching and checking of valid words.


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫