LeetCode Prob.30 Substring with Concatenation of All Words

題目描述

給定一個字符串 s 和一些長度相同的單詞 words蓬衡。找出 s 中恰好可以由 words 中所有單詞串聯(lián)形成的子串的起始位置碎捺。

注意子串要與 words 中的單詞完全匹配,中間不能有其他字符惨篱,但不需要考慮 words 中單詞串聯(lián)的順序盏筐。

來源:此題在力扣中國的鏈接

解題原理

正式開始之前,再次呼吁:檢查空輸入砸讳。

算法原型

我們先對著樣例思考一下如何解決這道題:

輸入:
s = "barfoothefoobarman",
words = ["foo","bar"]
輸出:[0,9]


  1. 不談動規(guī)琢融,那么必然是要用循環(huán)遍歷整個字串。
  2. 考慮到單詞的長度是相同的簿寂,我們可以記錄下單詞的長度wlen漾抬,然后在循環(huán)檢測的過程中,我們可以讓步長直接為wlen常遂。
wlen == 3

in loop k:
...foobar...
   ↑

in loop k + 1
...foobar...
      ↑
  1. 為了記錄當前到底檢測通過了幾個詞纳令,我們可以設(shè)置一個計數(shù)器,記錄已經(jīng)記錄了多少個詞克胳。如果計數(shù)器等于words的長度泊碑,那么就證明我們已經(jīng)完成了目標
words = ['bar', 'foo']
#wordsLen == 2
counter = 0

in loop 0:
barfoo...
↑
counter == 1

in loop 1:
barfoo
   ↑
counter == 2 == wordsLen

此時counter == wordsLen,因此ans里面就增加一個0毯欣。

  1. 如果我們遇到了詞表外的單詞馒过,比如the,那么我們就continue酗钞,跳過這個詞腹忽。同時来累,由于這個間隔的存在,我們需要清空counter窘奏。

基于上面這個思想嘹锁,我們可以寫出以下偽代碼:

for i in range(0, stringLen, wlen):
    if s.getword(i) in words:
        counter += 1
        if counter == wordsLen: 
            ans.append(substrHead(i)) 
            counter = 0
    else:
        counter = 0

return ans

然后我們著手優(yōu)化這個原型。

優(yōu)化1:words有重復(fù)詞

原題的樣例2就已經(jīng)告訴我們着裹,words內(nèi)是可以重復(fù)的:

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

那么該如何處理這個問題领猾?我們可以考慮把計數(shù)器counter改成一個字典,記錄下已經(jīng)記錄過了什么詞骇扇,以及記錄過了多少詞摔竿。

#設(shè)置比較對象
allwords = {}
for w in words:
    allwords[w] = allwords.get(w, 0) + 1
counter = {}

...in loop:
word = s.getword(i)
if word in words:
    counter[word] += 1
if counter == allwords:
    ans.append(substrHead(i))
    counter = {}

應(yīng)當指出,從這里開始少孝,substrHead是通過計算counter所記錄詞的個數(shù)继低,計算字串開頭在哪里。這對之后的優(yōu)化非常有幫助稍走。

foobarbarthe
         ↑
         i
對于不同的counter:
{'bar': 2, 'foo': 1} - substrHead(i) == 0
{'bar': 2, 'foo': 0} - substrHead(i) == 3

優(yōu)化2:考慮詞數(shù)過多

樣例2中有這么一段:

wordgoodgoodgood...

顯然袁翁,這里有太多good了。正好婿脸,我們在上面已經(jīng)設(shè)置了一個字典粱胜,只要判斷一下是否超過allwords里面的數(shù)字就行。

if word in words:
    if counter.get(word, 0) + 1 > allwords[word]:
        counter = {}
        counter[word] = 1
    else:
        counter[word] = counter.get(word, 0) + 1

重要優(yōu)化1:考慮重疊的可行解

在樣例1中狐树,可行解是這樣的:

[barfoo]the[foobar]man

然而年柠,如果我們輸入:

'barfoothebarfoo'
['bar', 'foo', 'the']

你會發(fā)現(xiàn),我們算法只能發(fā)現(xiàn)下面的可行解:

[barfoothe]barfoo

因為我們在檢測之后褪迟,就跳過了之前的全部字串!然而答憔,真實的可行解是這樣的:

[barfoothe]barfoo
bar[foothebar]foo
barfoo[thebarfoo]

為了解決這個問題味赃,我們可以做出進一步的改進。

需要用暴力的方法去一個一個鑒別嗎虐拓?那么我們就浪費了很多中間已經(jīng)檢測成立的信息心俗。所以,我們可以將檢測區(qū)域想象一個滑動的區(qū)域蓉驹,在一個可行解成立的時候城榛,我們只需要把字串頭前移wlen即可。

比如态兴,對于這個可行解:

[barfoothe]barfoo
counter = {'bar': 1, 'foo': 1, 'the': 1}

我們只需要將它變成:

bar[foothe]barfoo
counter = {'bar': 0, 'foo': 1, 'the': 1}

然后接著檢測就可以了狠持!下面就是對代碼的修改:

if counter == allwords:
    ans.append(substrHead(i))
    counter[s.getword(substrHead(i))] -= 1

優(yōu)化3:可行解與冗余詞的重疊

再看樣例2。重復(fù)的單詞瞻润,如果不是word喘垂,而是good甜刻,那么會發(fā)生什么呢?

在目前的算法下正勒,會出現(xiàn)這樣的情況:

[wordgoodgood]goodbestword
              ↑

詞數(shù)過多得院,我們清空了counter,然后發(fā)現(xiàn)章贞,剩下的goodbestword組不成可行解祥绞。

但是,我們自己看鸭限,是能看到可行解在哪里的:

wordgood[goodgoodbestword]

利用我們剛才滑動區(qū)域的思想蜕径,其實也可以解決這個問題。當詞數(shù)超出限制里覆,將字串頭移動到詞數(shù)符合限制為止即可丧荐。

if counter[word] + 1 > allwords[word]:
    while substrHead(i) != i and counter[word] + 1 > allwords[word]:
        counter[s.getword(substrHead(i))] -= 1
    counter[word] += 1
[wordgoodgood]goodbestword
              ↑
word[goodgood]goodbestword
              ↑
wordgood[good]goodbestword
              ↑
wordgood[goodgood]bestword
             ↑
wordgood[goodgood]bestword
                  ↑

重要優(yōu)化2:不定長的表外詞

其實我們剛才的討論都基于一個假設(shè):表外單詞是定長的,和表內(nèi)單詞長度相同喧枷。但是虹统,題目只提出了表內(nèi)單詞同長,并沒有提出表外單詞同長隧甚。所以车荔,該怎么解決像這樣的問題呢:

輸入:
s = "barfoothetafoobar",
words = ["foo","bar"]

我們已經(jīng)做了這么多優(yōu)化了,難道前功盡棄戚扳,重歸暴力嗎忧便?當然不行,記住帽借,要充分利用花了時間處理過的信息珠增。

我們剛才的算法處理了什么東西?是步長為wlen的條件下砍艾,整個字符串的可行解蒂教。

重點是,如果求過了s的可行解脆荷,再去求s[wlen:]的可行解凝垛,有什么區(qū)別?除了0蜓谋,兩組可行解沒有區(qū)別梦皮!

這就意味著,我們不需要將從頭到尾遍歷字符串桃焕,作為算法的開頭剑肯,而是只需要遍歷s[:wlen]就可以了!

例如观堂,當遍歷計數(shù)器j == 0時:

[bar][foo][the][taf][oob]ar
^^^^^^^^^^ 

而當j == 2時:

ba[rfo][oth][eta][foo][bar]
                 ^^^^^^^^^^ 

據(jù)此修改的代碼如下:

for j in range(wlen):
    i = j
    while i + wlen < slen:
        ...
        i += wlen
return ans

最終代碼

經(jīng)過以上針對原型的優(yōu)化退子,相信你也可以自己寫出這題的正確代碼了岖妄。這里提供一份LeetCode提供的50ms左右的樣例:

class Solution:
    def findSubstring(self, s: 'str', words: 'List[str]') -> 'List[int]':
        if not s or words==[]:
            return []
        lenstr=len(s)
        lenword=len(words[0])
        lensubstr=len(words)*lenword
        times={}
        for word in words:
            if word in times:
                times[word]+=1
            else:
                times[word]=1
        ans=[]
        for i in range(min(lenword,lenstr-lensubstr+1)):
            self.findAnswer(i,lenstr,lenword,lensubstr,s,times,ans)
        return ans
    def findAnswer(self,strstart,lenstr,lenword,lensubstr,s,times,ans):
        wordstart=strstart
        curr={}
        while strstart+lensubstr<=lenstr:
            word=s[wordstart:wordstart+lenword]
            wordstart+=lenword
            if word not in times:
                strstart=wordstart
                curr.clear()
            else:
                if word in curr:
                    curr[word]+=1
                else:
                    curr[word]=1
                while curr[word]>times[word]:
                    curr[s[strstart:strstart+lenword]]-=1
                    strstart+=lenword
                if wordstart-strstart==lensubstr:
                    ans.append(strstart)

這里還提供了一個小優(yōu)化:for i in range(min(lenword,lenstr-lensubstr+1)):,對應(yīng)我們剛才說的寂祥,就是單詞較長的時候荐虐,我們可以只需要遍歷到最終字串的長度已經(jīng)觸及字符串總長度的時候停下來:

"aaaaabbbbbcccccdd"
['aaaaa','bbbbb','ccccc']
lenword == wlen == 5
lenstr - lensubstr + 1 == slen - wlen * words + 1 == 3
取后者即可。

感謝閱讀丸凭!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末福扬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子惜犀,更是在濱河造成了極大的恐慌铛碑,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虽界,死亡現(xiàn)場離奇詭異汽烦,居然都是意外死亡,警方通過查閱死者的電腦和手機莉御,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進店門撇吞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人礁叔,你說我怎么就攤上這事牍颈。” “怎么了琅关?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵煮岁,是天一觀的道長。 經(jīng)常有香客問我涣易,道長画机,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任新症,我火速辦了婚禮步氏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘账劲。我一直安慰自己,他們只是感情好金抡,可當我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布瀑焦。 她就那樣靜靜地躺著,像睡著了一般梗肝。 火紅的嫁衣襯著肌膚如雪榛瓮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天巫击,我揣著相機與錄音禀晓,去河邊找鬼精续。 笑死,一個胖子當著我的面吹牛粹懒,可吹牛的內(nèi)容都是我干的重付。 我是一名探鬼主播,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼凫乖,長吁一口氣:“原來是場噩夢啊……” “哼确垫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起帽芽,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤删掀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后导街,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體披泪,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年搬瑰,在試婚紗的時候發(fā)現(xiàn)自己被綠了款票。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡跌捆,死狀恐怖徽职,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情佩厚,我是刑警寧澤氢烘,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站桅狠,受9級特大地震影響蚕泽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜钙姊,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一毯辅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧煞额,春花似錦思恐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至婚温,卻和暖如春描焰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背栅螟。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工荆秦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留篱竭,地道東北人。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓步绸,卻偏偏與公主長得像掺逼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子靡努,可洞房花燭夜當晚...
    茶點故事閱讀 43,554評論 2 349

推薦閱讀更多精彩內(nèi)容