Given a list of unique words, return all the possible word squares you can build from them.
A sequence of words forms a valid word square if the kth row and column read the exact same string, where 0 <= k < max(numRows, numColumns).
["ball","area","lead","lady"] forms a word square because each word reads the same both horizontally and vertically:
b a l l
a r e a
l e a d
l a d y
Example 1:
Input: words = ["area","ball","dear","lady","lead"] Output: [["ball","area","lead","lady"],["lady","area","dear","year"]] Explanation: The output consists of two word squares. The order of output does not matter.
Example 2:
Input: words = ["abat","baba","atan","atal"] Output: [["baba","abat","baba","atal"]] Explanation: The only word square is the following: baba abat baba atal
Constraints:
1 <= words.length <= 10001 <= words[i].length <= 5words[i] consists of only lowercase English letters.words[i] are unique.When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The brute force method to form word squares is to try all possible combinations of words. We'll explore every possible arrangement until we find solutions that fit the word square's constraints. Think of it as exhaustively checking every scenario.
Here's how the algorithm would work step-by-step:
def find_word_squares_brute_force(words):
results = []
word_length = len(words[0]) if words else 0
def backtrack(current_square):
if len(current_square) == word_length:
results.append(current_square[:])
return
# Iterate over all words to find a suitable candidate for the next row
for word in words:
if is_valid_word(current_square, word):
current_square.append(word)
backtrack(current_square)
current_square.pop()
def is_valid_word(current_square, word):
row_index = len(current_square)
# Ensures the new word aligns vertically with existing words
for column_index in range(row_index):
if current_square[column_index][row_index] != word[column_index]:
return False
return True
# Start the search with each word as the first row
for word in words:
backtrack([word])
return resultsThe key is to avoid checking every possible combination of words. We use a dictionary-like structure to quickly find words that *could* fit based on what we already have, and then intelligently build our word square line by line.
Here's how the algorithm would work step-by-step:
def wordSquares(words):
word_length = len(words[0])
result = []
# Create the prefix map for quick lookup.
prefix_map = {}
for word in words:
for i in range(1, word_length + 1):
prefix = word[:i]
if prefix not in prefix_map:
prefix_map[prefix] = []
prefix_map[prefix].append(word)
def backtrack(current_square):
if len(current_square) == word_length:
result.append(current_square[:])
return
# Determine the prefix for the next word.
prefix_to_match = "".join([word[len(current_square)] for word in current_square])
#Early termination if no words match current prefix
if prefix_to_match not in prefix_map:
return
#Explore all matching prefixes to find the valid word square.
for next_word_candidate in prefix_map[prefix_to_match]:
current_square.append(next_word_candidate)
backtrack(current_square)
current_square.pop()
#Iterate to pick different starting words.
for word in words:
backtrack([word])
return result| Case | How to Handle |
|---|---|
| Empty input word list | Return an empty list immediately as no word square is possible. |
| Input word list contains empty strings | Filter out empty strings from the input list to avoid incorrect prefix matching. |
| Input word list contains strings of varying lengths | Only consider word lists where all words have the same length; otherwise, return an empty list. |
| No valid word square exists for the given input | The backtracking algorithm should explore all possibilities and return an empty list if no solution is found. |
| Word list contains duplicate words | The algorithm should correctly handle duplicate words, potentially leading to multiple valid word squares or no valid square at all, depending on the other words. |
| Maximum word length or word list size leading to stack overflow during recursion | Implement iterative deepening depth-first search or consider memoization to limit recursion depth and prevent stack overflow. |
| Very large input word list and large word size impacting performance significantly | Optimize prefix searching using a Trie data structure to improve the efficiency of finding suitable words during backtracking. |
| Integer overflow when calculating string hash if used for Trie operations | Use appropriate data types and modulo operations or choose a more robust hashing algorithm to prevent integer overflow. |