Bootstrap

leetcode 30.串联所有单次的子串

题目:

给定一个字符串 s 和一个字符串数组 words words 中所有字符串 长度相同

 s 中的 串联子串 是指一个包含  words 中所有字符串以任意顺序排列连接起来的子串。

  • 例如,如果 words = ["ab","cd","ef"], 那么 "abcdef", "abefcd""cdabef", "cdefab""efabcd", 和 "efcdab" 都是串联子串。 "acdbef" 不是串联子串,因为他不是任何 words 排列的连接。

返回所有串联子串在 s 中的开始索引。你可以以 任意顺序 返回答案。

题目实例:

示例 1:
输入:s = "barfoothefoobarman", words = ["foo","bar"]
输出:[0,9]
解释:因为 words.length == 2 同时 words[i].length == 3,连接的子字符串的长度必须为 6。
子串 "barfoo" 开始位置是 0。它是 words 中以 ["bar","foo"] 顺序排列的连接。
子串 "foobar" 开始位置是 9。它是 words 中以 ["foo","bar"] 顺序排列的连接。
输出顺序无关紧要。返回 [9,0] 也是可以的。
示例 2:
输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
输出:[]
解释:因为 words.length == 4 并且 words[i].length == 4,所以串联子串的长度必须为 16。
s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。
所以我们返回一个空数组。
示例 3:
输入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
输出:[6,9,12]
解释:因为 words.length == 3 并且 words[i].length == 3,所以串联子串的长度必须为 9。
子串 "foobarthe" 开始位置是 6。它是 words 中以 ["foo","bar","the"] 顺序排列的连接。
子串 "barthefoo" 开始位置是 9。它是 words 中以 ["bar","the","foo"] 顺序排列的连接。
子串 "thefoobar" 开始位置是 12。它是 words 中以 ["the","foo","bar"] 顺序排列的连接。

代码:

class Solution(object):
    def findSubstring(self, s, words):
        """
        :type s: str
        :type words: List[str]
        :rtype: List[int]
        """
        l_word=len(words[0])
        l_words=len(words)
        l_s=len(s)
        ans=[]
        if l_s<l_word:#不符合直接返回空
            return ans

        strandard_dic={} #真值字典
        for word in words:
            if word in strandard_dic:
                strandard_dic[word]+=1
            else:
                strandard_dic[word]=1

        for i in range(l_word):
            star=i #左初始指针
            j=i #右遍历指针
            count=0
            count_dic={} #临时字典
            while j+l_word<=l_s: #窗口大小为l_word长度
                temp_word=s[j:j+l_word] #窗口值
                if temp_word in strandard_dic: #跟真值字典值对比
                    count+=1 #记录当前指针内部含有WORD数目
                    if temp_word not in count_dic: #如果存在临时字典加1
                        count_dic[temp_word]=1 
                    else:
                        count_dic[temp_word]+=1
                    while count_dic[temp_word]>strandard_dic[temp_word]: #如果当前临时字典大的话,左指针就向右,一直遍历到当前临时字典小于等于真值字典
                        temp_wordV2=s[star:star+l_word]
                        star+=l_word
                        count-=1
                        count_dic[temp_wordV2]-=1
                    if count==l_words:#等于的话说明正好随机匹配上,左指针向左步进一格
                        ans.append(star)
                        temp_wordV2=s[star:star+l_word]
                        count_dic[temp_wordV2]-=1
                        count-=1
                        star+=l_word

                    j+=l_word #不管如何右指针一直遍历
                else: #如果不在左指针直接回来,左右指针重合
                    star=j+l_word
                    j=star
                    count=0
                    count_dic.clear()
        return ans

作者:陈明义
链接:https://leetcode.cn/problems/substring-with-concatenation-of-all-words/solutions/2814617/python98-by-chen-ming-yi-4-dczm/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

代码思想

本题设计重点在于右指针遍历,左指针跟进,同时步进窗口大小为单个word长度。

要想满足所有窗口均被遍历,则左右指针一共需要需要遍历单个word长度大小次数。

即代码的整个外循环for i in range(l_word)

代码设计:

设定左指针start,右指针j,记录真实word情况的哈希表,以及临时记录的哈希表

同时还有记录当前临时单次个数的count

所遇到的情况:

1.右指针遍历单词满足在哈希表中,count+1,然后对应临时哈希表+1

如果对应真值继续向右遍历,如果大于说明多填充,则左指针向右遍历,直至将多余剔除。

2.右指针遍历单次满足在哈希表中,count+1,然后对应临时哈希表+1,

同时count大小对应words长度,说明正好满足,左指针加入结果,然后向右步进,同时删除对应临时字典值

3.如果当前数值不满足,则说明当前遍历字符不可能满足对应随机排布,左右指针汇合,重新开始步进。

时间复杂度: 𝑂(𝑁⋅𝑀⋅𝐿)O(N⋅M⋅L),其中 N 是字符串 s 的长度,M 是 words 的长度,L 是每个单词的长度。

我自己的代码,不如上面这个,我总结一下缺点,可以过,但是时间多个4倍:

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        numdiction={}
        zhenzhizidian={}
        for word in words:
            zhenzhizidian[word]=zhenzhizidian.get(word,0)+1
        countnum=len(words)
        zilength=len(words[0])
        totallength=len(s)
        result=[]
        tmpcount=0
        tmpstr=''
        count=0
        if totallength<zilength*countnum:
            return []
        for idx in range(zilength):
            numdiction.clear()
            count=0
            tmpcount=0
            tmpstr=''
            start=idx
            for i in range(idx,totallength):#start开始的
                tmpstr+=s[i]
                count+=1
                if count==zilength: #每次等于子串长度判断
                    numdiction[tmpstr]=numdiction.get(tmpstr,0)+1
                    tmpcount+=1
                    tmpstr=''
                    count=0
                if tmpcount==countnum: #窗口满足数目及长度
                    if all(numdiction.get(word)==zhenzhizidian[word] for word in words):
                        result.append(start)
                    for j in range(start,start+zilength):
                        tmpstr+=s[j]
                    numdiction[tmpstr]-=1
                    tmpstr=''
                    tmpcount-=1
                    start+=zilength
        return result

代码思想

设计的跟上面的代码差不多,我当时也是按照左右指针以及窗口设计,但是没有左右指针没有遍历l_word长度,导致没有遍历所有应该的窗口。主要差距在代码逻辑上。

代码逻辑:

我是每次从起始点到重点遍历,然后用临时字符串保存,将对应字符串的值加到字典里,

如果临时保存的words数目等于真实words数目,对比字典值,如果不满足,左指针步进一个窗口大小,然后右指针步进

这样设计会导致多进行很多操作,每次都要满足真实words长度都会检查

上面代码设计将左右指针步进进行了优化

重复,报错左指针就会跟进,省去了很多不必要的操作

把大窗口进行拆分小窗口,判断每个小窗口遇到的情况,当小窗口都满足时,大窗口一定满足

我的代码时间复杂度O(N⋅(N/L+M⋅L)),其中 N 是字符串 s 的长度,M 是 words 的长度,L 是每个单词的长度

;