[力扣]30. 串联所有单词的子串

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        res = []
        m,w = len(words),len(words[0])
        M = {}
        for it in words:
            if it not in M.keys(): M[it] = 1
            else: M[it] += 1
        for i in range(w):
            S = {}
            j,cnt = i,0
            while j + w <= len(s):
                if w * m <= j:
                    t = s[j - w * m : j - w * (m - 1)]
                    S[t] -= 1
                    if t in M.keys() and S[t] < M[t]: cnt -= 1
                t = s[j:j + w]
                if t not in S.keys(): S[t] = 1
                else: S[t] += 1
                if t in M.keys() and S[t] <= M[t]:cnt += 1
                if m == cnt:
                    res.append(j - w * (m - 1))
                j += w
        return res
  • 1 <= s.length <= 104
  • s 由小写英文字母组成
  • 1 <= words.length <= 5000
  • 1 <= words[i].length <= 30

从数据范围不难看出,如果使用朴素做法,这道题无疑会TLE。
本题可以借助哈希来实现,哈希运算的时间复杂度可以近似看为常数时间,所以对于本题非常适用。

我们使用一个字典M,记录words中每个字符串出现的次数,字典的键为words中的每一个字符串,值为对应字符串出现的次数。

因为words中的每个字符串的长度都是相等的,所以匹配的子串必定在子串中占据相同的长度。我们只需要逐段地进行匹配即可。
在一层循环内部,我们使用了滑动窗口算法,字典S用于匹配字符串中每段w长的子串出现的次数。每一次进行匹配字符串为s[j - (m - 1) * w:j + w],下标j每次会向后移动w个单位,当新的窗口长度大于目标子串长度w * m时,会从窗口的左边部分删除一个w长度的字符串,以保证新的待匹配子串和目标长度保持一致。

在一次循环内部,我们逐段进行了枚举。在最外层循环,我们只需枚举从0到w-1的偏移量,便可实现对所有可能的字符串的枚举。

另外,在哈希时需要注意,题目并没有说words中的字符串不存在重复,所以我们不排除words中有多个相同字符串的可能。因此我们不能将

for it in words:
	if it not in M.keys(): M[it] = 1            
	else: M[it] += 1

简写为

for it in words:M[it] = 1
if t in M.keys() and S[t] < M[t]: cnt -= 1

也是同样的道理。


题目链接
原创不易,感谢支持!

你可能感兴趣的:(leetcode,python,题解,leetcode,散列表,哈希算法,字符串,算法)