1. 程式人生 > >[leetcode] 30. 與所有單詞相關聯的字串(cn第653位做出此題的人~)

[leetcode] 30. 與所有單詞相關聯的字串(cn第653位做出此題的人~)

next() equal fab 用例 tro nat -c false ati

30. 與所有單詞相關聯的字串

技術分享圖片

技術分享圖片

這個題做了大概兩個小時左右把。。。嚴重懷疑leetcode的judge機器有問題。同樣的代碼交出來不同的運行時長,能不能A題還得看運氣?

大致思路是,給words生成一關於s的字典,用來記錄每個word在s中出現的所有位置,註意可能會出現相同的word。然後遞歸枚舉words的排列情況,一一校驗是否符合條件(即連在一起)。用到了遞歸+記憶化搜索+kmp+幾個剪枝

一直最後幾個測試用例上TLE,囧啊,甚至一度懷疑是不是還有更優的做法。

然後開始考慮剪枝:

  1. 記憶化搜索:即同一層下同樣的word只搜一次就好。(又多過了幾個測試樣例)
  2. 使用kmp算法:一開始在生成字典時直接用的java String的indexOf,效率太低了,換用kmp後,又多過了兩個,不過還是不夠
  3. 在確定頭位置時,如果此時s剩余長度根本不夠words的所有長度,直接跳過 ,媽的,此時發現還有最後兩個測試用例過不了。。。仔細研究了下數據,又搞出一個特判剪枝2333333333
  4. 當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位做出此題的人~)