[leetcode] 30. 與所有單詞相關聯的字串(cn第653位做出此題的人~)
阿新 • • 發佈:2018-07-06
next() equal fab 用例 tro nat -c false ati
30. 與所有單詞相關聯的字串
這個題做了大概兩個小時左右把。。。嚴重懷疑leetcode的judge機器有問題。同樣的代碼交出來不同的運行時長,能不能A題還得看運氣?
大致思路是,給words生成一關於s的字典,用來記錄每個word在s中出現的所有位置,註意可能會出現相同的word。然後遞歸枚舉words的排列情況,一一校驗是否符合條件(即連在一起)。用到了遞歸+記憶化搜索+kmp+幾個剪枝
一直最後幾個測試用例上TLE,囧啊,甚至一度懷疑是不是還有更優的做法。
然後開始考慮剪枝:
- 記憶化搜索:即同一層下同樣的word只搜一次就好。(又多過了幾個測試樣例)
- 使用kmp算法:一開始在生成字典時直接用的java String的indexOf,效率太低了,換用kmp後,又多過了兩個,不過還是不夠
- 在確定頭位置時,如果此時s剩余長度根本不夠words的所有長度,直接跳過 ,媽的,此時發現還有最後兩個測試用例過不了。。。仔細研究了下數據,又搞出一個特判剪枝2333333333
- 當words只有兩種時,即word1與word2,那麽可以先驗證下word1+word2或word2+word1是否是s子串,如果不是的話不用搜了,肯定搜不到答案。。。
class Solution { class KMPStringMatcher { /** * 獲取KMP算法中pattern字符串對應的next數組 * * @param p 模式字符串對應的字符數組 * @return */ protected int[] getNext(char[] p) { // 已知next[j] = k,利用遞歸的思想求出next[j+1]的值 // 如果已知next[j] = k,如何求出next[j+1]呢?具體算法如下: // 1. 如果p[j] = p[k], 則next[j+1] = next[k] + 1; // 2. 如果p[j] != p[k], 則令k=next[k],如果此時p[j]==p[k],則next[j+1]=k+1, // 如果不相等,則繼續遞歸前綴索引,令 k=next[k],繼續判斷,直至k=-1(即k=next[0])或者p[j]=p[k]為止 int pLen = p.length; int[] next = new int[pLen]; int k = -1; int j = 0; next[0] = -1; // next數組中next[0]為-1 while (j < pLen - 1) { if (k == -1 || p[j] == p[k]) { k++; j++; next[j] = k; } else { k = next[k]; } } return next; } public int indexOf(String source, String pattern) { int i = 0, j = 0; char[] src = source.toCharArray(); char[] ptn = pattern.toCharArray(); int sLen = src.length; int pLen = ptn.length; int[] next = getNext(ptn); while (i < sLen && j < pLen) { // 如果j = -1,或者當前字符匹配成功(src[i] = ptn[j]),都讓i++,j++ if (j == -1 || src[i] == ptn[j]) { i++; j++; } else { // 如果j!=-1且當前字符匹配失敗,則令i不變,j=next[j],即讓pattern模式串右移j-next[j]個單位 j = next[j]; } } if (j == pLen) return i - j; return -1; } } public List<Integer> findSubstring(String s, String[] words) { List<Integer> ans = new ArrayList<>(); if (s.equals("")) { return ans; } if (words.length == 0) { return ans; } Map<String, List<Integer>> dic = new HashMap<>(); KMPStringMatcher kmpStringMatcher = new KMPStringMatcher(); for (String word : words) { if (dic.containsKey(word)) { // word 可能出現重復的 continue; } int k = 0, p = 0; int tmp = 0; while ((tmp = kmpStringMatcher.indexOf(s.substring(p), word)) != -1) { k = p + tmp; dic.computeIfAbsent(word, x -> new ArrayList<Integer>()).add(k); p = k + 1; } } Set<String> keys = dic.keySet(); if (keys.size() == 2) { Iterator<String> iterator = keys.iterator(); String tmp1 = iterator.next(); String tmp2 = iterator.next(); if (kmpStringMatcher.indexOf(s, tmp1 + tmp2) == -1 && kmpStringMatcher.indexOf(s, tmp2 + tmp1) == -1) { return new ArrayList<>(); } } Map<String, Integer> flagMap = new HashMap<>(); List<String> standBy = new ArrayList<>(Arrays.asList(words)); for (String s1 : standBy) { if (!dic.containsKey(s1)) break; //記憶化搜索剪枝:同一層下同樣的word沒必要繼續搜下去 if (flagMap.containsKey(s1)) continue; flagMap.put(s1, 1); List<String> tmpStandBy = new ArrayList<>(standBy); tmpStandBy.remove(s1); for (Integer p : dic.get(s1)) { // 剪枝,如果字串長度根本不夠匹配的,直接跳過 if (s.length() - (p + s1.length()) < tmpStandBy.size() * s1.length()) continue; dfs(dic, tmpStandBy, p, p + s1.length(), s1.length(), ans); } } // return ans.stream().distinct().collect(Collectors.toList()); return ans; } // p 當前子串的頭位置,now表示當前子串的尾位置 public boolean dfs(Map<String, List<Integer>> dic, List<String> standby, int p, int now, int len, List<Integer> ans) { if (standby.size() == 0) { ans.add(p); return true; } Map<String, Integer> flagMap = new HashMap<>(); for (String s1 : standby) { if (!dic.containsKey(s1)) break; //記憶化搜索剪枝:同一層下同樣的word沒必要繼續搜下去 if (flagMap.containsKey(s1)) continue; flagMap.put(s1, 1); boolean flag = false; for (Integer position : dic.get(s1)) { if (position == now) { flag = true; break; } } if (flag) { List<String> tmpStandBy = new ArrayList<>(standby); tmpStandBy.remove(s1); if (dfs(dic, tmpStandBy, p, now + len, len, ans)) { return true; } } } return false; } }
另外,看別人題解貌似可以用滑動窗口的思路來做,回頭研究下
[leetcode] 30. 與所有單詞相關聯的字串(cn第653位做出此題的人~)