徒手挖地球十九周目

徒手挖地球十九周目

NO.30 串聯所有單詞的子串 困難

在這裏插入圖片描述

這道題要把每個單詞看成整體,每個不同的單詞看作是不同的字符,單詞串就看成是特殊的字符串。

注意:s中的單詞未必是長度相等。words中可能存在相同的單詞。

思路一:暴力法 words中的單詞長度都一樣,大幅降低了這道題的難度,所以這個特點要充分利用。所以遍歷s的每個子串,分別檢查每個字串中是否符合要求。

用一個hashmap存儲words中的每個單詞及其在words中出現的次數;每遍歷一個子串都要用一個hashmap存儲被遍歷子串中出現的words中存在的單詞及其在子串中出現的次數。

重點是理解這個“要求”:1.words中的每個單詞都必須出現一次。2.words中的每個單詞必須連續出現

反言之:檢查每個子串的過程中,出現words中的不存在的單詞則結束檢查;出現與words中相等的單詞,但是出現的次數超過其在words中出現的次數則結束檢查。

public List<Integer> findSubstring(String s, String[] words) {
    List<Integer> res=new ArrayList<>();
    if (words==null||words.length==0)return res;
    //單詞個數、單詞長度
    int wordNum = words.length,wordLen=words[0].length();
    //將words每個單詞及其個數存入hashmap
    HashMap<String,Integer> allWords=new HashMap<>();
    for (String word : words) {
        Integer value = allWords.getOrDefault(word, 0);
        allWords.put(word,++value);
    }
    //遍歷s每一個子串,剩餘不足wordNum*wordLen個字符的子串不需要遍歷
    for (int i = 0; i < s.length() - wordNum * wordLen + 1; i++) {
        //將子串中出現的和words中相等的單詞及其出現次數存入hashmap
        HashMap<String,Integer> hasWords=new HashMap<>();
        //記錄字串中和words中相等單詞數量
        int count=0;
        //統計字串中連續和words中相等的單詞
        while (count<wordNum){
            String word = s.substring(i + count * wordLen, i + (count + 1) * wordLen);
            //如果word匹配words中的單詞,就統計其出現次數
            if (allWords.containsKey(word)){
                Integer value = hasWords.getOrDefault(word, 0);
                hasWords.put(word,++value);
                //如果word出現次數超過words中這個單詞的總數量則結束統計
                if (hasWords.get(word)>allWords.get(word))break;
            }else {
                //如果字串中出現於words中所有單詞都不匹配的word則結束統計
                break;
            }
            //增加成功與words中匹配的單詞數量
            count++;
        }
        if (count==wordNum)res.add(i);
    }
    return res;
}

時間複雜度:O(n*m) n是s長度,m是words中單詞個數。

思路二:滑動窗口優化暴力法 用循環內的map(haswords)來保存窗口中匹配的單詞,再用一個指針標記窗口當前的起始位置。

暴力方法中有幾個需要優化的地方:

  1. 匹配成功:

    3My6je.png

    判斷i=0這個子串符合要求,如果繼續按照思路一的方法判斷。當i=3的時候,依然一次校驗每個單詞,但是“foofoo”這兩個單詞已經在i=0子串的時候校驗過了。所以暴力法中的hasword這個map並不需要每次都清空,只需要移除“bar“之後,從i=9的單詞開始判斷就好了。

  2. 匹配失敗,有不匹配的單詞:

    在這裏插入圖片描述

    判斷i=0子串時出現了“the”這個不匹配的單詞導致匹配失敗。i=3、i=6這些子串都包含“the”這個單詞,所以都不能匹配成功,所以窗口直接移動到i=9繼續校驗即可。

  3. 匹配失敗,單詞匹配但是數量超出:

    在這裏插入圖片描述

    i=0字串中“bar”出現兩次,但是words中只有一個"bar"所以匹配失敗。窗口移動到i=3,移除了“foo”但是“bar”依然多出一個,所以一定不匹配。窗口移動到i=6的時候移除了“bar”,就可以按照正常流程繼續判斷了。

不難發現,上述幾種情況的描述時,不再是每次移動一個字符,而是每次移動單詞長度。但是s中的單詞不一定都是剛好符合wordLen,如何解決這種情況?

答:分成wordLen種情況,分別進行判斷。分別從i=0開始每次移動一個單詞長度、從i=1開始每次移動一個單詞長度、從i=2開始每次移動一個單詞長度、、、直至從i=wordLen-1開始每次移動一個單詞長度。

public List<Integer> findSubstring(String s, String[] words) {
    List<Integer> result=new ArrayList<>();
    if (s==null||words==null||words.length==0)return result;
    int wordsNum = words.length,wordLen=words[0].length();
    //將words中的單詞及其數量存入hashmap
    HashMap<String,Integer> allWords=new HashMap<>();
    for (String word : words) {
        Integer value = allWords.getOrDefault(word, 0);
        allWords.put(word,value+1);
    }
    //分成wordLen中情況,分別從0開始每次移動一個單詞長度~從wordLen-1開始每次移動一個單詞長度
    for (int j=0;j<wordLen;j++){
        //haswords存放當前子串中匹配的單詞及其個數,count當前子串匹配的單詞數量
        HashMap<String,Integer> haswords=new HashMap<>();
        int count=0;
        //遍歷從j開始的每個子串,每次動一個單詞長度
        for (int i=j;i<s.length()-wordLen*wordsNum+1;i+=wordLen){
            //防止情況三出現之後,情況一繼續移除
            boolean hasRemoved=false;
            while (count<wordsNum){
                String curWord = s.substring(i + count * wordLen, i + (count + 1) * wordLen);
                //當前單詞匹配,加入haswords
                if (allWords.containsKey(curWord)) {
                    Integer value = haswords.getOrDefault(curWord, 0);
                    haswords.put(curWord,value+1);
                    count++;
                    //情況三,當前單詞匹配,但是數量超了
                    if (haswords.get(curWord) > allWords.get(curWord)) {
                        hasRemoved=true;
                        //從i開始逐個單詞,從haswords中移除,removeNum記錄移除的單詞個數
                        int removeNum=0;
                        while (haswords.get(curWord) > allWords.get(curWord)) {
                            String fristWord = s.substring(i + removeNum * wordLen, i + (removeNum + 1) * wordLen);
                            Integer v = haswords.get(fristWord);
                            haswords.put(fristWord,v-1);
                            removeNum++;
                        }
                        //移除完畢之後,更新count
                        count-=removeNum;
                        //移動i的位置(注意removeNum要-1,因爲跳出當前循環之後,i還要+wordLen)
                        i+=(removeNum-1)*wordLen;
                        break;
                    }
                }else{//情況二,當前單詞不匹配
                    //清空haswords
                    haswords.clear();
                    //i移動到當前單詞位置(因爲跳出當前循環之後,i還要+wordLen)
                    i+=count*wordLen;
                    count=0;
                    break;
                }
            }
            //情況一,匹配成功
            if (count==wordsNum)result.add(i);
            //如果情況三沒有出現
            if (count>0&&!hasRemoved){
                //移除成功匹配子串的第一個元素
                String fristWord = s.substring(i, i + wordLen);
                Integer v = haswords.get(fristWord);
                haswords.put(fristWord,v-1);
                count--;
            }
        }
    }
    return result;
}

時間複雜度:O(n*wordLen) 這個時間複雜度不敢確定算的對。。。


思路二的代碼,確實非常冗雜。接受批評o(╥﹏╥)o

本人菜鳥,有錯誤請告知,感激不盡!

更多題解和學習記錄博客:博客github

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章