当前位置: 首页 > article >正文

Leetcode 串联所有单词的子串

在这里插入图片描述

算法思想(中文解释)

这道题目要求我们在字符串 s 中找到所有子串,这些子串是字符串数组 words 中所有单词的串联,并且每个单词只能使用一次,且顺序可以任意。下面是代码的算法思想:


1. 核心思路

分解问题

  • 因为每个单词长度相同,我们可以使用一个滑动窗口(Sliding Window)来检查所有可能的子串。
  • 判断一个子串是否是所有单词的串联,可以通过比较单词的频次。

2. 步骤讲解

(1)初始化:
  • 单词长度:用 wordLength 表示 words 中每个单词的长度(因为题目保证它们长度相同)。
  • 总串联长度totalLength = wordLength * words.length,因为子串的长度一定是所有单词的长度之和。
  • 构造单词频次表:用 wordMap 记录 words 中每个单词的出现次数,便于后续比较。
(2)滑动窗口遍历:
  • 从字符串 s 的每个位置开始,以长度为 totalLength 的窗口进行检查:
    • 提取窗口中的子串 sub
    • 检查这个子串是否包含了 words 中所有单词且频次正确。
(3)子串验证:
  • 对于窗口中的子串 sub,将其按照 wordLength 分割成一个个小单词。
  • 检查这些小单词是否在 wordMap 中,并验证它们的出现频次是否超出限制:
    • 如果某个单词不在 wordMap 中,立即返回 false
    • 如果某个单词出现的次数超过了在 wordMap 中的次数,也返回 false
  • 如果所有单词验证通过,则说明当前窗口位置是符合要求的,记录下起始索引。

3. 时间复杂度分析

  1. 构造单词频次表O(m),其中 mwords 的长度(即单词个数)。
  2. 滑动窗口遍历:外层遍历最多需要 n - totalLength + 1 次,n 是字符串 s 的长度。
  3. 验证子串:每次验证需要遍历窗口中的所有单词,复杂度为 O(m * wordLength)

因此,总复杂度为
[ O((n - totalLength + 1) \cdot m \cdot wordLength) ]
通常可以简化为 O(n * m),适用于 s 较长和 words 较短的场景。


4. 代码逻辑解释

主函数:
  1. wordMap:统计 words 中每个单词的出现频次。
  2. 滑动窗口遍历:通过 for 循环,遍历从 0 到 s.length() - totalLength 的所有可能起始位置。
  3. 子串验证:调用辅助函数 isValid() 检查是否符合要求。
辅助函数 isValid
  1. 将子串 sub 分割成长度为 wordLength 的小单词。
  2. 使用 seen 哈希表记录窗口内每个单词的频次。
  3. wordMap 进行比较,判断是否匹配。

5. 关键优化点

  1. 滑动窗口:避免暴力检查所有子串,只检查可能的窗口,减少不必要的计算。
  2. 哈希表:使用 wordMapseen 快速判断频次关系,而不是逐一比较。
  3. 提前退出:在验证过程中,一旦发现不匹配的单词,立即退出验证,避免冗余计算。

6. 适用场景

该算法非常适用于以下情况:

  • 单词长度固定,字符串较长。
  • words 中的单词个数适中(否则频次表的维护开销较大)。

通过滑动窗口和哈希表的结合,这个算法能够高效解决题目要求。

java solution

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> result = new ArrayList<>();
        if(s == null || s.length() == 0 || words.length == 0 || words == null) return result;

        //初始化辅助变量
        int wordsLength = words.length;
        int wordLength = words[0].length();
        int totalLength = wordLength * wordsLength;

        //创建频率统计哈希表
        Map<String, Integer> freq = new HashMap<>();
        for(String word:words) {
            freq.put(word, freq.getOrDefault(word, 0) + 1);
        }

        //变量字符串s
        for(int i = 0; i <= s.length() - totalLength; i++) {
            //首先获取窗口内的子串
            String sub = s.substring(i, i + totalLength); //substring 是左闭右开
            //然后验证此时窗口内的子串
            if(isValid(sub, freq, wordLength)) {
                result.add(i);
            }
        }
        return result;
    }
    private boolean isValid(String sub, Map<String, Integer> freq, int wordLength) {
        Map<String, Integer> seen = new HashMap<>(); //存储子串中的单词频次
        for(int j = 0; j < sub.length(); j += wordLength) {
            //提取子串中的单词
            String word = sub.substring(j, j + wordLength);
            if(!freq.containsKey(word)) { //如果这个单词不在freq频率表中,
                return false;
            }
            seen.put(word, seen.getOrDefault(word, 0) + 1); //更新seen中的频次
            if(seen.get(word) > freq.get(word)) { //如果频次超过freq的限制
                return false; 
            }
        }
        return true;
    }
}

182 个测试用例通过了 181 个,被全 a 的测试用例卡住了(超时),


http://www.kler.cn/a/446620.html

相关文章:

  • 线性代数期末总复习的点点滴滴(1)
  • ubuntu22.04编译安装Opencv4.8.0+Opencv-contrib4.8.0教程
  • go-zero负载均衡实现原理
  • linux zip unzip 命令的使用
  • 前端小白学习之路-Vben探索 vite 配置 - 1/50
  • 202412月最新植物大战僵尸杂交版【V3.0.1】更新内容与下载
  • 【windows】sonarqube起不来的问题解决
  • 人脸修复与增强腾讯开源项目GFPGAN介绍
  • python rabbitmq实现简单/持久/广播/组播/topic/rpc消息异步发送可配置Django
  • Java爬虫实战:深度解析Lazada商品详情
  • 解决ubuntu22.04常见问题
  • Qt之串口设计-线程实现(十二)
  • 深入浅出:内网黄金票据与白银票据
  • ubuntu批量依赖库拷贝(ldd)
  • S32K324 Stack异常分析及解决方案
  • 数据结构十大排序之(冒泡,快排,并归)
  • C#.NET使用multipart/form-data方式上传文件及其他数据
  • 全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(实战训练一)
  • c4d动画怎么导出mp4视频,c4d动画视频格式设置
  • 网络安全基础知识分享
  • 算法—有效的字母异位词
  • 一文速通 IIC I2C子系统驱动 通信协议原理 硬件 时序 深度剖析
  • 青少年编程与数学 02-004 Go语言Web编程 14课题、数据操作
  • K8s docker-compose的入门
  • Yolov11学习笔记
  • 使用Redis提升PHP应用的性能