Guess the Word
HardPrompt
You are given an array of unique strings words where words[i] is six letters long. One word of words was chosen as a secret word.
You are also given the helper object Master. You may call Master.guess(word) where word is a six-letter-long string, and it must be from words. Master.guess(word) returns:
-1ifwordis not fromwords, or- an integer representing the number of exact matches (value and position) of your guess to the secret word.
There is a parameter allowedGuesses for each test case where allowedGuesses is the maximum number of times you can call Master.guess(word).
For each test case, you should call Master.guess with the secret word without exceeding the maximum number of allowed guesses. You will get:
"Either you took too many guesses, or you did not find the secret word."if you calledMaster.guessmore thanallowedGuessestimes or if you did not callMaster.guesswith the secret word, or"You guessed the secret word correctly."if you calledMaster.guesswith the secret word with the number of calls toMaster.guessless than or equal toallowedGuesses.
The test cases are generated such that you can guess the secret word with a reasonable strategy (other than using the bruteforce method).
Example 1:
Input: secret = "acckzz", words = ["acckzz","ccbazz","eiowzz","abcczz"], allowedGuesses = 10
Output: You guessed the secret word correctly.
Explanation:
master.guess("aaaaaa") returns -1, because "aaaaaa" is not in wordlist.
master.guess("acckzz") returns 6, because "acckzz" is secret and has all 6 matches.
master.guess("ccbazz") returns 3, because "ccbazz" has 3 matches.
master.guess("eiowzz") returns 2, because "eiowzz" has 2 matches.
master.guess("abcczz") returns 4, because "abcczz" has 4 matches.
We made 5 calls to master.guess, and one of them was the secret, so we pass the test case.Example 2:
Input: secret = "hamada", words = ["hamada","khaled"], allowedGuesses = 10
Output: You guessed the secret word correctly.
Explanation: Since there are two words, you can guess both.
Constraints:
1 <= words.length <= 100words[i].length == 6words[i]consist of lowercase English letters.- All the strings of
wordlistare unique. secretexists inwords.10 <= allowedGuesses <= 30
Approaches
2 approaches with complexity analysis and trade-offs.
This approach works by iteratively reducing the pool of possible secret words. We start with the entire word list. In each step, we pick a word from the current pool, guess it, and get the number of matching characters from the Master interface. This information is then used to filter the pool, keeping only the words that have the same number of matches with our guessed word. The process repeats with the smaller pool until the secret word is found.
Algorithm
-
- Initialize a list
possibleWordswith all words from the inputwords.
- Initialize a list
-
- Loop up to
allowedGuessestimes.
- Loop up to
-
- Select a random word
guessWordfrompossibleWords.
- Select a random word
-
- Call
master.guess(guessWord)to get the number ofmatches.
- Call
-
- If
matchesis 6, the secret is found, so terminate.
- If
-
- Create a new list
nextPossibleWords.
- Create a new list
-
- Iterate through each
wordin the currentpossibleWordslist.
- Iterate through each
-
- If the number of matches between
wordandguessWordis equal to thematchesreturned by the master, addwordtonextPossibleWords.
- If the number of matches between
-
- Replace
possibleWordswithnextPossibleWordsfor the next iteration.
- Replace
Walkthrough
The core idea is to prune the search space after every guess.
/** * // This is the Master's API interface. * // You should not implement it, or speculate about its implementation * interface Master { * public int guess(String word) {} * } */class Solution { public void findSecretWord(String[] words, Master master) { List<String> possibleWords = new ArrayList<>(Arrays.asList(words)); Random rand = new Random(); for (int i = 0; i < 30; i++) { // At most 30 guesses allowed // Pick a random word from the current list of possibilities. String guessWord = possibleWords.get(rand.nextInt(possibleWords.size())); int matches = master.guess(guessWord); // If we found the secret word, we are done. if (matches == 6) { return; } // Filter the list of possible words. List<String> nextPossibleWords = new ArrayList<>(); for (String word : possibleWords) { if (getMatches(guessWord, word) == matches) { nextPossibleWords.add(word); } } possibleWords = nextPossibleWords; } } // Helper function to count matches between two words. private int getMatches(String word1, String word2) { int matches = 0; for (int i = 0; i < word1.length(); i++) { if (word1.charAt(i) == word2.charAt(i)) { matches++; } } return matches; }}The choice of the guess word is crucial. In this simple version, we pick a word randomly from the current set of candidates. While this is better than blind guessing, it's not optimal. A poorly chosen guess might not significantly reduce the size of the candidate list, risking exceeding the allowedGuesses limit.
Complexity
Time
O(G * N * L), where G is the number of guesses made (at most `allowedGuesses`), N is the initial number of words, and L is the length of each word. In each guess, we iterate through the current list of possible words (at most N) and compare each with the guessed word (which takes O(L) time).
Space
O(N * L) to store the list of possible words. In each iteration, a new list is created, but the old one is discarded, so the peak space usage remains O(N * L).
Trade-offs
Pros
Simpler to implement than more advanced strategies.
Effectively reduces the search space compared to a brute-force approach.
Cons
The random selection of the guess word is not optimal.
It may fail to find the secret word within the allowed number of guesses if a series of unlucky random choices are made.
This approach refines the elimination strategy by making the smartest possible guess at each step. The 'smartest' guess is the one that minimizes the size of the candidate list in the worst-case scenario. To find this guess, we evaluate every word in the current candidate list. For each potential guess, we calculate how it would partition the other candidates based on the number of matches (0 to 6). We then choose the word that minimizes the size of the largest resulting partition. This minimax strategy guarantees the most significant reduction of the search space, regardless of the master's feedback.
Algorithm
-
- Initialize a list
possibleWordswith all words from the inputwords.
- Initialize a list
-
- Loop up to
allowedGuessestimes as long aspossibleWordsis not empty.
- Loop up to
-
- Find the best guess:
- a. For each
word1inpossibleWords, calculate how it partitions the list. - b. To do this, create a frequency map (or an array
groupsof size 7). - c. For each
word2inpossibleWords, calculatematches = getMatches(word1, word2)and incrementgroups[matches]. - d. Find the maximum value in
groups. This is the worst-case size of the next candidate list if we guessword1. - e. Keep track of the
word1that results in the minimum worst-case size. This is ourbestGuess. -
- Call
master.guess(bestGuess)to get the actual number ofmatches.
- Call
-
- If
matchesis 6, the secret is found, so terminate.
- If
-
- Filter
possibleWordsto create a new list containing only words that have exactlymatcheswithbestGuess.
- Filter
-
- Continue the loop with the new, smaller
possibleWordslist.
- Continue the loop with the new, smaller
Walkthrough
The key is to select a guess word that is most effective at distinguishing between the remaining possibilities.
/** * // This is the Master's API interface. * // You should not implement it, or speculate about its implementation * interface Master { * public int guess(String word) {} * } */class Solution { public void findSecretWord(String[] words, Master master) { List<String> possibleWords = new ArrayList<>(Arrays.asList(words)); for (int i = 0; i < 30 && !possibleWords.isEmpty(); i++) { String guessWord = findBestGuess(possibleWords); int matches = master.guess(guessWord); if (matches == 6) { return; } List<String> nextPossibleWords = new ArrayList<>(); for (String word : possibleWords) { if (getMatches(guessWord, word) == matches) { nextPossibleWords.add(word); } } possibleWords = nextPossibleWords; } } private String findBestGuess(List<String> wordlist) { if (wordlist.size() <= 2) { return wordlist.get(0); } String bestGuess = null; int minMaxGroupSize = Integer.MAX_VALUE; for (String word1 : wordlist) { int[] groups = new int[7]; // groups[i] = count of words with i matches for (String word2 : wordlist) { if (word1.equals(word2)) continue; groups[getMatches(word1, word2)]++; } int maxGroupSize = 0; for (int size : groups) { if (size > maxGroupSize) { maxGroupSize = size; } } if (maxGroupSize < minMaxGroupSize) { minMaxGroupSize = maxGroupSize; bestGuess = word1; } } return bestGuess; } private int getMatches(String word1, String word2) { int matches = 0; for (int i = 0; i < word1.length(); i++) { if (word1.charAt(i) == word2.charAt(i)) { matches++; } } return matches; }}The findBestGuess function is the core of this strategy. It iterates through every candidate word1 and simulates guessing it. For each word1, it checks against every other candidate word2 to see how they would be grouped by match count. The goal is to find a word1 where the largest of these groups is as small as possible. This ensures that even in the worst case (where the secret lies in the largest group), we have pruned the search space effectively.
Complexity
Time
O(G * N^2 * L), where G is the number of guesses (at most `allowedGuesses`), N is the number of words in the current list, and L is the word length. The dominant operation is `findBestGuess`, which involves a nested loop over the `possibleWords` list (O(N^2)) and a string comparison inside (O(L)). This is performed for each guess.
Space
O(N * L) to store the list of possible words. The `groups` array in `findBestGuess` uses constant space O(1) as the word length is fixed.
Trade-offs
Pros
Optimal strategy in terms of minimizing the number of guesses required in the worst case.
Guaranteed to find the solution within the given constraints due to its efficient pruning of the search space.
Cons
Higher computational cost per guess (O(N^2 * L)) compared to simpler strategies.
Solutions
Solution
/** * // This is the Master's API interface. * // You should not implement it, or speculate about its implementation * interface Master { * public int guess(String word) {} * } */ class Solution { public void findSecretWord ( String [] wordlist , Master master ) { int length = wordlist . length ; int [][] sameCounts = new int [ length ][ length ]; for ( int i = 0 ; i < length ; i ++) sameCounts [ i ][ i ] = 6 ; for ( int i = 0 ; i < length ; i ++) { String word1 = wordlist [ i ]; for ( int j = i + 1 ; j < length ; j ++) { String word2 = wordlist [ j ]; sameCounts [ i ][ j ] = sameCount ( word1 , word2 ); sameCounts [ j ][ i ] = sameCounts [ i ][ j ]; } } boolean [] candidates = new boolean [ length ]; Arrays . fill ( candidates , true ); int index = 0 ; while ( index >= 0 && index < length ) { index = findNext ( sameCounts , candidates ); if ( index < 0 ) break ; int guessResult = master . guess ( wordlist [ index ]); if ( guessResult == 6 ) break ; for ( int i = 0 ; i < length ; i ++) { if ( candidates [ i ] && sameCounts [ index ][ i ] != guessResult ) candidates [ i ] = false ; } } } public int sameCount ( String word1 , String word2 ) { int count = 0 ; for ( int i = 0 ; i < 6 ; i ++) { if ( word1 . charAt ( i ) == word2 . charAt ( i )) count ++; } return count ; } public int findNext ( int [][] sameCounts , boolean [] candidates ) { int length = candidates . length ; int min = Integer . MAX_VALUE ; int minIndex = - 1 ; for ( int i = 0 ; i < length ; i ++) { if ( candidates [ i ]) { int [] counts = new int [ 7 ]; for ( int j = 0 ; j < length ; j ++) { if ( j != i && candidates [ j ]) { int count = sameCounts [ i ][ j ]; counts [ count ]++; } } int max = 0 ; for ( int num : counts ) max = Math . max ( max , num ); if ( max < min ) { min = max ; minIndex = i ; } } } return minIndex ; } }// OJ: https://leetcode.com/problems/guess-the-word/ // Time: O(N) // Space: O(1) class Solution { int countSame ( string & a , string & b ) { int cnt = 0 ; for ( int i = 0 ; i < 6 ; ++ i ) cnt += a [ i ] == b [ i ]; return cnt ; } public: void findSecretWord ( vector < string >& A , Master & master ) { int N = A . size (); while ( true ) { string w = A [ rand () % N ]; int cnt = master . guess ( w ), len = 0 ; if ( cnt == 6 ) break ; for ( int i = 0 ; i < N ; ++ i ) { if ( countSame ( A [ i ], w ) == cnt ) swap ( A [ i ], A [ len ++ ]); } N = len ; } } };Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.