Given a set of words without duplicates, find all 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).
For example, the word sequence ["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
Notice
- There are at least 1 and at most 1000 words.
- All words will have the exact same length.
- Word length is at least 1 and at most 5.
- Each word contains only lowercase English alphabet
a-z
.
Example
Given a set ["area","lead","wall","lady","ball"]
return [["wall","area","lead","lady"],["ball","area","lead","lady"]]
Explanation: The output consists of two word squares. The order of output does not matter (just the order of words in each word square matters). Given a set ["abat","baba","atan","atal"] return [["baba","abat","baba","atan"],["baba","abat","baba","atal"]] Explanation: The output consists of two word squares. The order of output does not matter (just the order of words in each word square matters).
Solution 1. Depth First Search
1.Enumerate one word for each row.
2. After enumerating n words(n is the length of each word), check if the square is a word square.
If it is, add it to the final result.
3. Repeat the above steps until we've searched all possible squares.
The following code gets all possible combinations first, then check one combination.
It causes a Memory Limit ExceededError
as
the comb alone uses O(pow(words.length, words[0].length)) memory.
1 public class Solution { 2 public List<List<String>> wordSquares(String[] words) { 3 List<List<String>> result = new ArrayList<List<String>>(); 4 if(words == null || words.length == 0){ 5 return result; 6 } 7 int len = words[0].length(); 8 List<List<String>> comb = new ArrayList<List<String>>(); 9 getAllCombinations(len, words, comb, new ArrayList<String>()); 10 for(List<String> list : comb){ 11 if(isWordSquare(list)){ 12 result.add(list); 13 } 14 } 15 return result; 16 } 17 private void getAllCombinations(int len, String[] words, List<List<String>> comb, List<String> list){ 18 if(list.size() == len){ 19 comb.add(new ArrayList<String>(list)); 20 return; 21 } 22 for(int i = 0; i < words.length; i++){ 23 list.add(words[i]); 24 getAllCombinations(len, words, comb, list); 25 list.remove(list.size() - 1); 26 } 27 } 28 private boolean isWordSquare(List<String> list){ 29 for(int i = 0; i < list.size(); i++){ 30 String curr = list.get(i); 31 StringBuilder sb = new StringBuilder(); 32 for(int j = 0; j < list.size(); j++){ 33 sb.append(list.get(j).charAt(i)); 34 } 35 if(!curr.equals(sb.toString())){ 36 return false; 37 } 38 } 39 return true; 40 } 41 }
Solution 2. DFS with memory optimization
Each time we get a new combination, instead of store it, check if it is a word square "on the fly".
This optimization resolves the memory limit exceeded error. However, this algorithm is still very slow as it
still needs to check all possible combinations.
1 public class Solution { 2 public List<List<String>> wordSquares(String[] words) { 3 List<List<String>> result = new ArrayList<List<String>>(); 4 if(words == null || words.length == 0){ 5 return result; 6 } 7 int len = words[0].length(); 8 getAllCombinations(len, words, result, new ArrayList<String>()); 9 return result; 10 } 11 private void getAllCombinations(int len, String[] words, List<List<String>> result, List<String> list){ 12 if(list.size() == len){ 13 if(isWordSquare(list)){ 14 result.add(new ArrayList<String>(list)); 15 } 16 return; 17 } 18 for(int i = 0; i < words.length; i++){ 19 list.add(words[i]); 20 getAllCombinations(len, words, result, list); 21 list.remove(list.size() - 1); 22 } 23 } 24 private boolean isWordSquare(List<String> list){ 25 for(int i = 0; i < list.size(); i++){ 26 String curr = list.get(i); 27 StringBuilder sb = new StringBuilder(); 28 for(int j = 0; j < list.size(); j++){ 29 sb.append(list.get(j).charAt(i)); 30 } 31 if(!curr.equals(sb.toString())){ 32 return false; 33 } 34 } 35 return true; 36 } 37 }
Solution 3. DFS with Trie to avoid unecessarry work
Based on the definition of a word square, we conclude that once we fix some words on the first few rows, we can limit our search for the remaining unfilled rows to words
that only meet certain prefix requirement.
Take the following as an example, we observe the following search criteria.
b a l l
a r e a
l e a d
l a d y
If "ball" fills the 1st row, then words that can fill the 2nd row must have a prefix of "a";
If "area" fills the 2nd row, then words that can fill the 3rd row must have a prefix of "le";
If "lead" fills the 3rd row, then words that can fill the 4th row must have a prefix of "lad";
How do we use this observation to optimize Solution 2's running time?
1. Use a Trie to store all words that have the same prefix pre.(pre will be different depending how many rows have been filled with words)
2. Each time when searching for a word to fill the next empty row, if the next empty row must be filled with a word that has
a prefix of pre, then we only need to search from words that have pre as prefix.
1 class TrieNode{ 2 List<String> startWith; 3 TrieNode[] children; 4 TrieNode(){ 5 this.startWith = new ArrayList<String>(); 6 this.children = new TrieNode[26]; 7 } 8 } 9 10 class Trie{ 11 TrieNode root; 12 Trie(String[] words){ 13 this.root = new TrieNode(); 14 for(String s : words){ 15 TrieNode curr = root; 16 for(char c : s.toCharArray()){ 17 int idx = c - 'a'; 18 if(curr.children[idx] == null){ 19 curr.children[idx] = new TrieNode(); 20 } 21 curr.children[idx].startWith.add(s); 22 curr = curr.children[idx]; 23 } 24 } 25 } 26 List<String> findByPrefix(String prefix){ 27 List<String> ans = new ArrayList<String>(); 28 TrieNode curr = root; 29 for (char c : prefix.toCharArray()) { 30 int idx = c - 'a'; 31 if (curr.children[idx] == null){ 32 return ans; 33 } 34 curr = curr.children[idx]; 35 } 36 ans.addAll(curr.startWith); 37 return ans; 38 } 39 } 40 41 public class Solution { 42 public List<List<String>> wordSquares(String[] words) { 43 List<List<String>> result = new ArrayList<List<String>>(); 44 if(words == null || words.length == 0){ 45 return result; 46 } 47 int len = words[0].length(); 48 Trie dict = new Trie(words); 49 List<String> ansBuilder = new ArrayList<String>(); 50 for(String s : words){ 51 ansBuilder.add(s); 52 search(len, dict, result, ansBuilder); 53 ansBuilder.remove(ansBuilder.size() - 1); 54 } 55 return result; 56 } 57 private void search(int len, Trie dict, List<List<String>> result, List<String> ansBuilder){ 58 if(ansBuilder.size() == len){ 59 result.add(new ArrayList<String>(ansBuilder)); 60 return; 61 } 62 int idx = ansBuilder.size(); 63 StringBuilder prefixBuilder = new StringBuilder(); 64 for(String s : ansBuilder){ 65 prefixBuilder.append(s.charAt(idx)); 66 } 67 List<String> startWith = dict.findByPrefix(prefixBuilder.toString()); 68 for(String s : startWith){ 69 ansBuilder.add(s); 70 search(len, dict, result, ansBuilder); 71 ansBuilder.remove(ansBuilder.size() - 1); 72 } 73 } 74 }
Related Problems