【LeetCode難題解題思路(Java版)】30. 與所有單詞相關聯的字串

題目:

給定一個字符串 s 和一些長度相同的單詞 words。在 s 中找出可以恰好串聯 words 中所有單詞的子串的起始位置。

注意子串要與 words 中的單詞完全匹配,中間不能有其他字符,但不需要考慮 words 中單詞串聯的順序。

示例 1:

輸入:
  s = "barfoothefoobarman",
  words = ["foo","bar"]
輸出: [0,9]
解釋: 從索引 09 開始的子串分別是 "barfoor""foobar" 。
輸出的順序不重要, [9,0] 也是有效答案。
示例 2:

輸入:
  s = "wordgoodstudentgoodword",
  words = ["word","student"]
輸出: []

然後是輸入輸出:

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        
    }
}

注意到的關鍵點:

1.在模式串words裏所有的字符串長度是相等的。
2.若是s的長度小於所有word加在一起的長度,則不可能匹配成功。
3.因爲返回值是list,所以要持續查找,而不是找到就立即return。

方案初步設計

首先,將各個單詞排列組合,拼接得出字符串tmp,再去拿tmp和匹配串s去做匹配的方法肯定是不行的,因爲光是組合這一步,複雜度就是Sita(n!)了,這太可怕了。
1.首先可以找到所有單詞在s的開始位置的list,然後將所有的開始位置排序。
2.第二步,進行字符組的匹配,匹配方法是:
假如輸入是:

  s = "barfoothefoobarman",
  words = ["foo","bar"]

那麼對foo查找,搜索結果是[3,9],對bar查找,搜索結果是[0,12]。然後將list排序,得出[0,3,9,12]。而現在知道word的長度是3,那麼0和3可以拼一個結果,9和12可以拼一個結果。那麼結果就是[0,9]。

方案進一步研究

我在提交的時候,發現了這樣的測試用例:

 s = "aaaaaaaaa",
 words = ["aaa","aaa"]

就是words裏可能有重複的字符串,這時候,除了list之外,還應該設計一個HashMap<String,Integer> wordMap,其key是相應的字符串,其value是在words裏有多少個重複,比如現在的結果就是wordMap={‘aaa’:2}。
在這個基礎上對第一個’aaa’進行查找,結果是list=[0,1,2,3,4,5,6],然後在這裏會發現,第二個是不用再找的,在寫程序的時候可以用containsKey()來判定一下即可。
然後再對這個list判斷,在這裏會再需要另一個HashMap,其結構是HashMap<Integer,String> intMap,其key是s的字符的位置,其value值是以這個位置開始,存在着哪個word。比如{0:‘aaa’},表示在s的第0位上存在者’aaa’這個word。
這樣就判斷,從list的0開始,從intMap發現0存在着‘aaa’,則wordMap變成[‘aaa’:1],然後list往後走3位,list裏存在3,而在intMap裏發現3是’aaa’的開始,則wordMap變成[‘aaa’:0]。然後發現所有的word都用上了,則匹配成功了,然後以此類推做其他操作即可。
到此爲止,方案的通用性全面得到解決。
代碼如下:

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> list=new ArrayList<Integer>();//保存有單詞的位置
        Map<String, Integer> wordMap = new HashMap<>();// 存儲 word中各個單詞的個數
        Map<Integer,String> intMap = new HashMap<>();// 存儲 位置上保存了什麼單詞
        if(words.length<=0){
            return list;
        }
        int len=words[0].length();
        int count=words.length;
        int now_list;
        if(s.length()<count*len){
            return list;
        }
        for(int i=0;i<words.length;i++){
            if(words[i].length()!=len){
                return list;
            }
        }//以上是處理異常
        for (String word : words) {
            if (wordMap.containsKey(word)) {
                wordMap.put(word, wordMap.get(word) + 1);
                continue;
            }
            now_list=list.size();
            wordMap.put(word, 1);
            find(list,intMap,s,word);
            if(list.size()==now_list){
                return new ArrayList<Integer>();//若是有一個單詞沒找到,則直接宣佈不可能
            }
	    }
      Collections.sort(list);//將找到的所有位置排序
      int j=0;//list的下標
      int k=0;//往後的位移
      Map<String, Integer> tmpMap = new HashMap<>();// 用於深拷貝,以免破壞數據
      List<Integer> res=new ArrayList<Integer>();//最終的結果
      for(j=0;j<list.size();j++){
          int tmp=list.get(j);
          if(tmp<(s.length()-len*count+1)){
              tmpMap.putAll(wordMap);//深拷貝
              if(check(list,intMap,tmpMap,len,count,tmp)){
                  res.add(tmp);
              }
          }else{
              break;//到了這個位置,長度就不夠了
          }
      }
    return res;
    }
    public boolean check(List<Integer> list,Map<Integer,String> intMap,Map<String, Integer> wordMap,int len,int count,int start){//用來判斷是不是可以組成匹配字符串
        boolean res=true;
        String word="";
        int num=0;
        int i=0;
        for(i=0;i<count;i++){
            if(intMap.containsKey(start+i*len)){
                word=intMap.get(start+i*len);//得到對應的單詞
            }else{
                return false;
            }
            
            if(wordMap.containsKey(word)&&wordMap.get(word)>0){
                wordMap.put(word, wordMap.get(word) -1);
            }else{
                return false;    
            }
        }
        if(i<count-1){
            res=false;
        }
        return res;
        
    }
    
   public void find(List<Integer> list,Map<Integer,String> intMap,String s,String word){
        int i=0,j=0;
        for(i=0;i<(s.length()-word.length()+1);i++){
            for(j=0;j<word.length();j++){
                if(s.charAt(i+j)!=word.charAt(j)){
                    break;
                }
            }
            if(j==word.length()){
                list.add(i);
                intMap.put(i,word);
            }
        }
    }
}

運行結果:

運行結果
結果比較令人失望,我第一時間想到的是,可能是因爲我的find函數是用的樸素法,所以纔會這麼慢,然後我找了一下字符串匹配的經典算法。對應Sunday算法的思想,寫了個Sunday函數,代碼如下:

public void Sunday(List<Integer> list,Map<Integer,String> intMap,String s,String word){
        Map<Character, Integer> charMap = new HashMap<>();// 存儲 word中各個字符
        for(int k=0;k<word.length();k++){//首先創建一個哈希表用來存word裏的所有字符,以及其出現的最後一個位置
                charMap.put(word.charAt(k),k);
        }
        int end=word.length()-1,count=0,tmp=0,len=word.length()-1;
        while(end<s.length()){
            tmp=end;
            len=word.length()-1;
            count=0;//計數器
            while(len>=0&&word.charAt(len)==s.charAt(tmp)){
                tmp--;
                len--;
                count++;
            }
            if(count==word.length()){
                list.add(tmp+1);
                intMap.put(tmp+1,word);
            }
            end++;//向後找一位
            while(end<s.length()&&!charMap.containsKey(s.charAt(end))){
                end++;
            }
          
            if(end==s.length()){
                break;
            }
            end=end+(word.length()-charMap.get(s.charAt(end)))-1;//向後移動
        }
    }

然後將上面的find替換爲Sunday,發現效果也不是特別明顯,可能我的List處理邏輯還需要優化,這次就先寫個文檔記錄一下。
參考鏈接:
1.4種字符串匹配算法
2.幾個高效的字符串匹配算法
3.Sunday算法—簡單高效的字符串匹配算法

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