LeetCode 第30题:串联所有单词的子串

LeetCode 第30题:串联所有单词的子串

题目描述

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

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

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

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

难度

困难

题目链接

点击在LeetCode中查看题目

示例

示例 1:

输入:s = "barfoothefoobarman", words = ["foo","bar"]
输出:[0,9]
解释:串联子串的起始位置为:
- 0:"barfoo" 是 ["bar","foo"] 的串联
- 9:"foobar" 是 ["foo","bar"] 的串联

示例 2:

输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
输出:[]
解释:不存在串联子串

提示

  • 1 <= s.length <= 104
  • 1 <= words.length <= 5000
  • 1 <= words[i].length <= 30
  • words[i]s 由小写英文字母组成

解题思路

方法:滑动窗口 + 哈希表

这是一道综合性的字符串处理题目,需要结合滑动窗口和哈希表来解决。

关键点:

  1. 所有单词长度相同,这是一个重要的条件
  2. 需要考虑所有可能的起始位置
  3. 使用哈希表记录单词出现次数

具体步骤:

  1. 预处理:统计words中每个单词的出现次数
  2. 滑动窗口:遍历所有可能的起始位置
  3. 验证过程:检查当前窗口中的单词是否符合要求

图解思路

滑动窗口过程表

步骤 窗口内容 单词分割 状态 说明
初始状态 "barfoo" ["bar","foo"] 有效 找到第一个有效位置 0
移动窗口 "arfoot" ["arf","oot"] 无效 不是有效的单词组合
继续移动 "foobar" ["foo","bar"] 有效 找到第二个有效位置 9

哈希表状态分析

阶段 目标哈希表 当前哈希表 匹配状态
初始化 {} 未开始
第一次匹配 完全匹配
中间状态 不匹配
第二次匹配 完全匹配

代码实现

public class Solution {
    public IList<int> FindSubstring(string s, string[] words) {
        List<int> result = new List<int>();
        if (string.IsNullOrEmpty(s) || words == null || words.Length == 0) {
            return result;
        }
      
        Dictionary<string, int> wordCount = new Dictionary<string, int>();
        foreach (string word in words) {
            if (!wordCount.ContainsKey(word)) {
                wordCount[word] = 0;
            }
            wordCount[word]++;
        }
      
        int wordLength = words[0].Length;
        int totalLength = wordLength * words.Length;
      
        for (int i = 0; i <= s.Length - totalLength; i++) {
            Dictionary<string, int> seenWords = new Dictionary<string, int>();
            int j;
          
            for (j = 0; j < words.Length; j++) {
                int startPos = i + j * wordLength;
                string currentWord = s.Substring(startPos, wordLength);
              
                if (!wordCount.ContainsKey(currentWord)) {
                    break;
                }
              
                if (!seenWords.ContainsKey(currentWord)) {
                    seenWords[currentWord] = 0;
                }
                seenWords[currentWord]++;
              
                if (seenWords[currentWord] > wordCount[currentWord]) {
                    break;
                }
            }
          
            if (j == words.Length) {
                result.Add(i);
            }
        }
      
        return result;
    }
}

执行结果

  • 执行用时:276 ms
  • 内存消耗:45.8 MB

代码亮点

  1. 🎯 使用哈希表高效统计单词频率
  2. 💡 滑动窗口优化时间复杂度
  3. 🔍 精确的单词匹配逻辑
  4. 🎨 清晰的代码结构和变量命名

常见错误分析

  1. 🚫 没有处理空字符串或空数组的情况
  2. 🚫 忘记检查单词长度是否相同
  3. 🚫 哈希表比较逻辑错误
  4. 🚫 子串长度计算错误

解法对比

解法 时间复杂度 空间复杂度 优点 缺点
暴力匹配 O(n * m * k) O(m * k) 实现简单 效率低
滑动窗口 O(n * k) O(m) 效率较高 实现复杂
优化滑动窗口 O(n) O(m) 效率最高 需要额外空间

相关题目

posted @   旧厂街小江  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示