LeetCode30

一個白饅頭發表於2020-10-07

LeetCode30 串聯所有單詞的子串

hashmap何時使用

在涉及字串S是否在字串P中被包含,或者尋找特定字元,只要是找值,或者是要求是否符合某個特徵,都可以用hashmap。
那麼本題利用hashmap更是一種巧妙地方法。演算法如下:
建立兩個hashmap,一個儲存單詞,一個儲存模式串中子串中單詞出現個數。
將所有單詞放到hashmap1中,並記錄所有單詞出現的次數。
接下來對子串進行掃描:
如果字串不出現在hashmap1中,則跳過進行下一輪迴圈,
如果在hashmap1中,放到hashmap2中,如果hashmap2與hashmap1中存放的內容一致時,即可滿足答案。

本題注意單詞長度相同。

解法一:

public List<Integer> findSubstring(String s, String[] words) {
    List<Integer> res = new ArrayList<Integer>();
    int wordNum = words.length;
    if (wordNum == 0) {
        return res;
    }
    int wordLen = words[0].length();
    //HashMap1 存所有單詞
    HashMap<String, Integer> allWords = new HashMap<String, Integer>();
    for (String w : words) {
        int value = allWords.getOrDefault(w, 0);
        allWords.put(w, value + 1);
    }
    //遍歷所有子串
    for (int i = 0; i < s.length() - wordNum * wordLen + 1; i++) {
        //HashMap2 存當前掃描的字串含有的單詞
        HashMap<String, Integer> hasWords = new HashMap<String, Integer>();
        int num = 0;
        //判斷該子串是否符合
        while (num < wordNum) {
            String word = s.substring(i + num * wordLen, i + (num + 1) * wordLen);
            //判斷該單詞在 HashMap1 中
            if (allWords.containsKey(word)) {
                int value = hasWords.getOrDefault(word, 0);
                hasWords.put(word, value + 1);
                //判斷當前單詞的 value 和 HashMap1 中該單詞的 value
                if (hasWords.get(word) > allWords.get(word)) {
                    break;
                }
            } else {
                break;
            }
            num++;
        }
        //判斷是不是所有的單詞都符合條件
        if (num == wordNum) {
            res.add(i);
        }
    }
    return res;
}

解法二:

解法二是對解法一的優化,因為解法一每次只是移動一個字元,浪費效率,
所以解法二每次移動單詞長度個字元。
但是要分情況進行討論:
請參考以下文章:
https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-6/
public List<Integer> findSubstring(String s, String[] words) {
    List<Integer> res = new ArrayList<Integer>();
    int wordNum = words.length;
    if (wordNum == 0) {
        return res;
    }
    int wordLen = words[0].length();
    HashMap<String, Integer> allWords = new HashMap<String, Integer>();
    for (String w : words) {
        int value = allWords.getOrDefault(w, 0);
        allWords.put(w, value + 1);
    }
    //將所有移動分成 wordLen 類情況
    for (int j = 0; j < wordLen; j++) {
        HashMap<String, Integer> hasWords = new HashMap<String, Integer>();
        int num = 0; //記錄當前 HashMap2(這裡的 hasWords 變數)中有多少個單詞
		//每次移動一個單詞長度
        for (int i = j; i < s.length() - wordNum * wordLen + 1; i = i + wordLen) {
            boolean hasRemoved = false; //防止情況三移除後,情況一繼續移除
            while (num < wordNum) {
                String word = s.substring(i + num * wordLen, i + (num + 1) * wordLen);
                if (allWords.containsKey(word)) {
                    int value = hasWords.getOrDefault(word, 0);
                    hasWords.put(word, value + 1);
                    //出現情況三,遇到了符合的單詞,但是次數超了
                    if (hasWords.get(word) > allWords.get(word)) {
                        // hasWords.put(word, value);
                        hasRemoved = true;
                        int removeNum = 0;
                        //一直移除單詞,直到次數符合了
                        while (hasWords.get(word) > allWords.get(word)) {
                            String firstWord = s.substring(i + removeNum * wordLen, i + (removeNum + 1) * wordLen);
                            int v = hasWords.get(firstWord);
                            hasWords.put(firstWord, v - 1);
                            removeNum++;
                        }
                        num = num - removeNum + 1; //加 1 是因為我們把當前單詞加入到了 HashMap 2 中
                        i = i + (removeNum - 1) * wordLen; //這裡依舊是考慮到了最外層的 for 迴圈,看情況二的解釋
                        break;
                    }
                //出現情況二,遇到了不匹配的單詞,直接將 i 移動到該單詞的後邊(但其實這裡
                //只是移動到了出現問題單詞的地方,因為最外層有 for 迴圈, i 還會移動一個單詞
                //然後剛好就移動到了單詞後邊)
                } else {
                    hasWords.clear();
                    i = i + num * wordLen;
                    num = 0;
                    break;
                }
                num++;
            }
            if (num == wordNum) {
                res.add(i);

            }
            //出現情況一,子串完全匹配,我們將上一個子串的第一個單詞從 HashMap2 中移除
            if (num > 0 && !hasRemoved) {
                String firstWord = s.substring(i, i + wordLen);
                int v = hasWords.get(firstWord);
                hasWords.put(firstWord, v - 1);
                num = num - 1;
            }

        }

    }
    return res;
}