leetcode425 - Word Squares - hard
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
Note:
1. There are at least 1 and at most 1000 words.
2. All words will have the exact same length.
3. Word length is at least 1 and at most 5.
4. Each word contains only lowercase English alphabet a-z.
DFS。with prune。
最基础的DFS思想就是,在某一中间状态,前面几行已经被一些单词填了,我只管接下来这行填什么。我可以试着把单词本里的单词都一个个试着放在这一行,接着dfs,再回溯。
但这种蛮力的dfs时间复杂度太高了,需要做一些提前prune。本题prune的基本思想是,行要依附于与列相等的规则。当我们最面几列的prefix是什么样已经定下来的时候,我们就可以强制要求后面几行的prefix是什么样,从而在以某些prefix开头的单词本dict根本不存在的时候,提前终结可能性。
prune1: 填第l行的时候,要和第l列读着一样,而第l列已经有了前面一些prefix了,读出这个prefix,接下来可以插入第l行的候选单词也要以这个prefix开头。
prune2: 填第l行第时候,不是所有上面以prefix开头的就可以了,还要进一步检查一下会不会坑队友。这一行填进去有0~l行了,那么l+1~n-1行的prefix也可以确定了,你确认一下是不是后面每行的prefix都还是有希望的,词典里有的。要是有任何一行被弄的没希望了,那你就算这行暂时加进去没问题,之后迟早一天注定会死,也还是就别加了。这个解法可以提前少掉后面几行(还没到注定要死行前)的好几种不同组合可能饿了。
细节:
1.额外写一个收集所有以某一prefix开头的单词的dict。类型<String, Set<String>>。注意所有原始单词都是以""前缀开头的。
2.这题一些固定的一直要递归用到的变量很多,都放在参数列表里有点冗余,其实可以提出来做全局变量。dict, n
实现:
class Solution { public List<List<String>> wordSquares(String[] words) { List<List<String>> ans = new ArrayList<>(); if (words == null || words.length == 0) { return ans; } int n = words[0].length(); Map<String, Set<String>> dict = prefixDict(words); dfs(new ArrayList<String>(), ans, 0, n, dict); return ans; } private void dfs(List<String> crt, List<List<String>> ans, int l, int n, Map<String, Set<String>> dict) { if (l == n) { ans.add(new ArrayList<String>(crt)); return; } String prefix = ""; for (int i = 0; i < l; i++) { prefix += crt.get(i).charAt(l); } if (!dict.containsKey(prefix)) { return; } Set<String> set = dict.get(prefix); for (String s : set) { if (!canTry(l, s, crt, dict, n)) { continue; } crt.add(s); dfs(crt, ans, l + 1, n, dict); crt.remove(crt.size() - 1); } } private Map<String, Set<String>> prefixDict(String[] words) { Map<String, Set<String>> map = new HashMap<>(); map.put("", new HashSet<String>()); for (String s : words) { String pref = ""; map.get(pref).add(s); for (int i = 0; i < s.length(); i++) { pref += s.charAt(i); map.putIfAbsent(pref, new HashSet<String>()); map.get(pref).add(s); } } return map; } private boolean canTry(int l, String nextWord, List<String> crt, Map<String, Set<String>> dict, int n) { for (int j = l + 1; j < n; j++) { String prefix = ""; for (int i = 0; i < l; i++) { prefix += crt.get(i).charAt(j); } prefix += nextWord.charAt(j); if (!dict.containsKey(prefix)) { return false; } } return true; } }