1. 程式人生 > >LeetCode HashTable 30 Substring with Concatenation of All Words

LeetCode HashTable 30 Substring with Concatenation of All Words

should pub str key integer ash arraylist nat character

You are given a string, s, and a list of words, words, that are all of the same length. Find all starting indices of substring(s) in s that is a concatenation of each word in words exactly once and without any intervening characters.

For example, given:
s: "barfoothefoobarman"
words: ["foo", "bar"]

You should return the indices: [0,9].
(order does not matter).

  這道題讓我們求串聯所有單詞的子串,就是說給定一個長字符串,再給定幾個長度相同的單詞,讓我們找出串聯給定所有單詞的子串的起始位置,我目前的能力只是寫出了一種可以通過168組數據的算法,還是的參考別人的算法思路和代碼,這裏先貼一下自己的求解的思路(為了不誤人子弟,可以跳過下面一段)。

  首先我們需要使用一個哈希表存取在words數組裏面出現過的單個字符串key,在value中存放字符串在words數組中出現的次數,使用計數器count記錄子串中出現的words裏面的字符串的個數,當count = words.length時,成功找到一個子串。定義left指針從左開始一個字符一個字符

遍歷,right只向子串的右部,第二層循環每次從S中取出給定長度的子串與哈希表中的鍵值進行比較:

  如果取出的子串是哈希表的鍵值:判斷該鍵值對應的value>0? 如果大於0,說明該鍵值在left--right子串中沒有出現,或者是在words數組中有重復,且目前允許重復。count++,right向右移動一定長度。否則說明left--right子串中不允許再出現該字符串了,這時break,跳出循環,重新初始化哈希表,count置0。

  如果取出的子串不是哈希表的鍵值,說明出現了不允許出現的字符,這個連續的子串已經不符合要求了,跳出循環,重新初始化哈希表,count置0。

  內層循環結束之後,說明重left開始的子串已經判斷完成,如果count == words.length則說明找到了一個符合條件的子串,否則判斷,count是否大於0,如果不大於0,說明哈希表沒有被更改過沒不需要重新初始化,否則初始化哈希表。

代碼如下:

public class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        int wlen = words[0].length();
        List<Integer> li = new ArrayList<Integer>();
        if(s.length()/wlen < words.length) // 長度不足,直接返回
            return li;
        HashMap<String,Integer> map = new HashMap<String,Integer>();
        initValue(map,words);
        int slen = s.length();
        int wordslen = wlen*words.length;
        for(int left = 0;left<=slen-wordslen;left++){
            int count = 0;
            int right = left;
            String sub = s.substring(right,right+wlen);
            while( map.containsKey(sub) && map.get(sub)>0 ){ //對於每一個;left right向後判斷子串是否符合條件
                map.put( sub,map.get(sub)-1 );
                count++;
                right += wlen;
                if(right + wlen>s.length()) break;
                sub = s.substring(right,right+wlen);
            }
            
            if(count == words.length)
                li.add(left);
            
            if(count>0){
                map.clear();
                initValue(map,words);
            }
        }
        return li;
        
    }
    public void initValue(HashMap map,String []words){ //初始化哈希表
        for(int i=0;i<words.length;i++){
            if( map.containsKey(words[i]) ){
                map.put( words[i],(int)map.get(words[i])+1 );
            }
            else{
                map.put( words[i],1 );
            }
        }
    }
}

  這種思路超時,後來查看別人的算法,才發現我們這是一個字符一個字符的遍歷,而更為巧妙的方式是一個單詞一個單詞的遍歷,感覺別人寫的思路挺明晰的,這裏引用一下[LeetCode] Substring with Concatenation of All Words 串聯所有單詞的子串 的分析思路:

  這道題還有一種O(n)時間復雜度的解法,設計思路非常巧妙,但是感覺很難想出來。這種方法不再是一個字符一個字符的遍歷,而是一個詞一個詞的遍歷,比如根據題目中的例子,字符串s的長度n為18,words數組中有兩個單詞(cnt=2),每個單詞的長度len均為3,那麽遍歷的順序為0,3,6,8,12,15,然後偏移一個字符1,4,7,9,13,16,然後再偏移一個字符2,5,8,10,14,17,這樣就可以把所有情況都遍歷到,我們還是先用一個哈希表m1來記錄words裏的所有詞,然後我們從0開始遍歷,用left來記錄左邊界的位置,count表示當前已經匹配的單詞的個數。然後我們一個單詞一個單詞的遍歷,如果當前遍歷的到的單詞t在m1中存在,那麽我們將其加入另一個哈希表m2中,如果在m2中個數小於等於m1中的個數,那麽我們count自增1,如果大於了,那麽需要做一些處理,比如下面這種情況, s = barfoofoo, words = {bar, foo, abc}, 我們給words中新加了一個abc,目的是為了遍歷到barfoo不會停止,那麽當遍歷到第二foo的時候, m2[foo]=2, 而此時m1[foo]=1,這是後已經不連續了,所以我們要移動左邊界left的位置,我們先把第一個詞t1=bar取出來,然後將m2[t1]自減1,如果此時m2[t1]<m1[t1]了,說明一個匹配沒了,那麽對應的count也要自減1,然後左邊界加上個len,這樣就可以了。如果某個時刻count和cnt相等了,說明我們成功匹配了一個位置,那麽將當前左邊界left存入結果res中,此時去掉最左邊的一個詞,同時count自減1,左邊界右移len,繼續匹配。如果我們匹配到一個不在m1中的詞,那麽說明跟前面已經斷開了,我們重置m2,count為0,左邊界left移到j+len,

代碼如下:

public class Solution {  
    public ArrayList<Integer> findSubstring(String S, String[] L) {  
    ArrayList<Integer> res = new ArrayList<Integer>();  
    if(S==null || S.length()==0 || L==null || L.length==0)  
        return res;  
    HashMap<String,Integer> map = new HashMap<String,Integer>();  
    for(int i=0;i<L.length;i++)   // 初始化哈希表,是每次判斷過程中的標準
    {  
        if(map.containsKey(L[i]))  
        {  
            map.put(L[i],map.get(L[i])+1);  
        }  
        else  
        {  
            map.put(L[i],1);  
        }  
    }  
    for(int i=0;i<L[0].length();i++)   // l[0].length代表 L 中每個字符串的長度,因為要通過,每次偏移一個字符來實現遍歷
    {  
        HashMap<String,Integer> curMap = new HashMap<String,Integer>(); // 心得哈希表是在每次向右遍歷過程中動態變化的
        int count = 0;  
        int left = i;  //left,right -->right 到達s的右側,代表偏移一個字符,所需要的遍歷結束
        for(int j=i;j<=S.length()-L[0].length();j+=L[0].length())
        {  
            String str = S.substring(j,j+L[0].length());  

            if(map.containsKey(str)) //取出的子串如果在words中 則更新 動態變化的哈希表
            {  
                if(curMap.containsKey(str))    //存在該鍵值
                    curMap.put(str,curMap.get(str)+1);
                else  
                    curMap.put(str,1);     //不存在該鍵值
                if(curMap.get(str)<=map.get(str)) //子串中 str 鍵值代表的 value 沒有達到飽和
                    count++;  
                else  
                {  //如果str代表的鍵值達到飽和,說明連續的子串中出現了字符串的重復,需要跳過之前出現過的str,通過循環left
                    while(curMap.get(str)>map.get(str))  //跳過其後的子串,更新curmap和count,並判斷其後指定長度的子串是                 
//否是str,如果不是,則繼續向右跳動指定長度,如果是,說明left後的子串left--right已經符合要求,達到了 == 剛好飽和 { String temp = S.substring(left,left+L[0].length()); if(curMap.containsKey(temp)) { curMap.put(temp,curMap.get(temp)-1); if(curMap.get(temp)<map.get(temp)) count--; } left += L[0].length(); } } if(count == L.length) //如果找到一個子串,left向右跳動指定長度,繼續遍歷。 { res.add(left); String temp = S.substring(left,left+L[0].length()); if(curMap.containsKey(temp)) curMap.put(temp,curMap.get(temp)-1); count--; left += L[0].length(); } } else //連續子串中出現了不允許出現的字符串,right之前的子串已經不可能符合條件,left直接跳到right+L[0].length() { curMap.clear(); count = 0; left = j+L[0].length(); } } } return res; } }

LeetCode HashTable 30 Substring with Concatenation of All Words