自学内容网 自学内容网

LeetCode【0030】串联所有单词的子串

1 中文题目

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

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

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

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

示例:

输入: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] 也是可以的。
输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
输出:[]
解释:因为 words.length == 4 并且 words[i].length == 4,所以串联子串的长度必须为 16。
s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。
所以返回一个空数组。
输入: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"] 顺序排列的连接。

提示:

  • 1 ≤ s . l e n g t h ≤ 1 0 4 1 \leq s.length \leq 10^4 1s.length104
  • 1 ≤ w o r d s . l e n g t h ≤ 5000 1 \leq words.length \leq 5000 1words.length5000
  • 1 ≤ w o r d s [ i ] . l e n g t h ≤ 30 1 \leq words[i].length \leq 30 1words[i].length30
  • words[i]s 由小写英文字母组成

2 求解方法:滑动窗口

2.1 方法思路

方法核心

使用滑动窗口和哈希表,按单词长度进行分组遍历,并动态维护当前窗口中的单词计数

实现步骤

(1)初始化:

  • 创建words中单词的频率字典
  • 计算关键参数(单词长度、单词数量等)

(2)滑动窗口:

  • 对每个可能的起始位置进行遍历
  • 维护当前窗口中的单词计数

(3)匹配过程:

  • 检查每个单词是否在目标词典中
  • 动态调整窗口大小
  • 记录有效的起始位置

方法示例

输入:s = "barfoothefoobarman", words = ["foo","bar"]

过程演示:
1. 初始化:
   word_len = 3
   word_count = 2
   word_dict = {"foo": 1, "bar": 1}

2. 第一轮遍历(i=0):
   检查: "bar" -> found
   检查: "foo" -> found, 添加索引0
   检查: "the" -> reset
   ...

3. 第二轮遍历(i=1)...

4. 第三轮遍历(i=2)...

返回:[0, 9]

2.2 Python代码

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        # 特殊情况处理
        if not s or not words:
            return []
            
        # 初始化结果列表
        result = []
        
        # 获取关键参数
        word_len = len(words[0])  # 每个单词的长度
        word_count = len(words)   # 单词的个数
        total_len = word_len * word_count  # 总长度
        s_len = len(s)  # 字符串长度
        
        # 如果字符串长度小于所需总长度,直接返回空列表
        if s_len < total_len:
            return []
            
        # 创建words中单词的计数字典
        word_dict = {}
        for word in words:
            word_dict[word] = word_dict.get(word, 0) + 1
            
        # 遍历所有可能的起始位置
        # 只需要遍历word_len个起始位置
        for i in range(word_len):
            # 初始化滑动窗口的左右边界
            left = i
            right = i
            
            # 当前窗口中的单词计数
            curr_dict = {}
            
            # 已匹配的单词数量
            count = 0
            
            # 右指针不超过字符串长度
            while right + word_len <= s_len:
                # 获取右侧单词
                word = s[right:right + word_len]
                right += word_len
                
                # 如果是目标单词
                if word in word_dict:
                    curr_dict[word] = curr_dict.get(word, 0) + 1
                    count += 1
                    
                    # 如果当前单词出现次数过多
                    while curr_dict[word] > word_dict[word]:
                        # 移除左侧单词直到符合要求
                        left_word = s[left:left + word_len]
                        curr_dict[left_word] -= 1
                        count -= 1
                        left += word_len
                        
                    # 如果找到所有单词
                    if count == word_count:
                        result.append(left)
                        # 移除最左侧的单词,继续寻找下一个匹配
                        left_word = s[left:left + word_len]
                        curr_dict[left_word] -= 1
                        count -= 1
                        left += word_len
                        
                else:
                    # 如果遇到非目标单词,重置窗口
                    left = right
                    curr_dict.clear()
                    count = 0
                    
        return result

2.3 复杂度分析

  • 时间复杂度:O(n * k),n是字符串长度,k是单个单词的长度
    • 实际上是O(n/k * k),因为每次移动k个字符
  • 空间复杂度:O(m)
    • m是words中不同单词的数量
    • 需要存储单词频率字典

3 题目总结

题目难度:困难
数据类型:字符串
数据结构:哈希表
应用算法:滑动窗口


原文地址:https://blog.csdn.net/qq_32882309/article/details/143738094

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!