1. 程式人生 > 其它 >【LeetCode】30.串聯所有單詞的子串

【LeetCode】30.串聯所有單詞的子串

30.串聯所有單詞的子串

知識點:字串;滑動視窗;雜湊表

題目描述

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

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

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

示例 2:
輸入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
輸出:[]

示例 3:
輸入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
輸出:[6,9,12]


解法一:滑動視窗

這其實也是滑動視窗的一道典型題目
找到包含words中單詞的子串;要注意到題目中一個條件,words中的單詞都是長度相同的,子串其實就可以去想想滑動視窗。
當然也可以直接看word中的長度,然後每次擷取這麼長的子串,然後統計子串中的單詞和個數,形成第二個雜湊表,看和word中的雜湊表是否相同。這樣的話兩次for。
當然,我們可以用滑動視窗來優化這道題。
初始:統計words中每個字元的數量,形成hashmap1;
視窗的移動都是以單詞的長度為單位進行的, 沒必要一個字元一個字元的去移動;
1.right右移,試圖去找滿足條件的視窗

if當前的單詞不在hashmap1中,那前面肯定都不對了,left直接移動到right即可;    
if當前的單詞在hashmap1中,並且還沒有超過hashmap1中的個數,那right可以繼續移動
if當前的單詞在hashmap1中,但是當前視窗中包含的目前這個單詞個數已經超過了hashmap1中的個數,那就要把當前這個單詞對應的前面的部分都移出去,也就是左視窗要移動    
所以怎麼能夠知道當前視窗內對應的單詞和個數呢,維持一個當前視窗的hashmap2,

2.left右移:

當現在的視窗內當前的單詞數量超過了需要的個數,那left要把這個單詞和其之前的單詞都移動出去(因為if left只移動一個,假設目前的這個單詞在後面重復,根本沒有意義,因為還是多著呢)。   
除此之外,維持一個所需要的單詞總數量,這樣可以知道什麼時候滿足要求,向res中新增答案
  • 其實仔細想一下這個過程,仍然是滿足滑動視窗的思想:毛毛蟲模型
    右視窗移動,尋找可行解,然後會破壞掉條件(對應本題就是出現了沒有的單詞或者單詞出現的過多了)
    然後右邊不動了,左視窗移動,讓整個視窗仍然滿足條件
    與之前的難點就在於在其中穿插了兩個雜湊表,然後在其中判斷視窗是否會滿足最後答案的要求。
from collections import Counter, defaultdict
class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        res = []
        allwords_count = Counter(words)  # 所有單詞的數量的雜湊表
        word_count = len(words)   # 單詞的數量
        oneword_len = len(words[0])   #每個單詞的長度
        for i in range(oneword_len):  #前面不清楚具體是由哪個字元開始的
            left = right = i
            count = 0   #已經滿足的單詞數量
            windowwords_count = defaultdict(int)  # 統計視窗內每個單詞數量的雜湊表
            while right + oneword_len <= len(s):
                cur_word = s[right:right+oneword_len]  #當前單詞
                right += oneword_len
                if cur_word not in allwords_count:  #肯定匹配失敗
                    left = right  #視窗右移
                    windowwords_count.clear() #字典清空
                    count = 0  
                else:  #有這個單詞
                    windowwords_count[cur_word] += 1
                    count += 1
                    while windowwords_count.get(cur_word) > allwords_count.get(cur_word):
                        popword = s[left: left+oneword_len]  #移出的單詞
                        windowwords_count[popword] -= 1 
                        count -= 1
                        left += oneword_len  #次數都減1,左視窗右移
                    if count == word_count:
                        res.append(left)
        return res