惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
The GitHub Blog
The GitHub Blog
F
Fortinet All Blogs
Cloudbric
Cloudbric
P
Palo Alto Networks Blog
T
Threatpost
T
Tor Project blog
T
Tenable Blog
AWS News Blog
AWS News Blog
Project Zero
Project Zero
L
LangChain Blog
Cyberwarzone
Cyberwarzone
Engineering at Meta
Engineering at Meta
雷峰网
雷峰网
C
CERT Recently Published Vulnerability Notes
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Security Latest
Security Latest
云风的 BLOG
云风的 BLOG
I
Intezer
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
P
Proofpoint News Feed
A
Arctic Wolf
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Google DeepMind News
Google DeepMind News
V
Vulnerabilities – Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
MongoDB | Blog
MongoDB | Blog
aimingoo的专栏
aimingoo的专栏
K
Kaspersky official blog
Jina AI
Jina AI
N
News | PayPal Newsroom
T
The Blog of Author Tim Ferriss
D
DataBreaches.Net
A
About on SuperTechFans
博客园 - 三生石上(FineUI控件)
博客园 - 【当耐特】
Hugging Face - Blog
Hugging Face - Blog
Recorded Future
Recorded Future
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
S
Secure Thoughts
TaoSecurity Blog
TaoSecurity Blog
P
Privacy & Cybersecurity Law Blog
P
Proofpoint News Feed
MyScale Blog
MyScale Blog
IT之家
IT之家
Forbes - Security
Forbes - Security
The Hacker News
The Hacker News
Last Week in AI
Last Week in AI
T
Threat Research - Cisco Blogs
Y
Y Combinator Blog

寒九

Service Worker缓存图片 – 寒九 ASP.NET Core中使用EF Core实现部分更新实体记录 – 寒九 微软商店开发者账户申请 – 寒九 数据库结构管理利器——数据库迁移(数据库模式迁移) – 寒九 JPEG/JPG/JFIF/TIFF/EXIF格式解析 – 寒九 音乐播放器核心设计——播放列表设计 – 寒九 LeetCode-10 正则表达式匹配题解 – 寒九 WinUI 3与UWP中GridView和ListView的性能优化 – 寒九 Async方法导致的死锁与无响应 – 寒九
LeetCode-30 串联所有单词的子串题解 – 寒九
HHao 文章: 41 · 2024-04-19 · via 寒九

设wordLength为单词长度,wordCount为单词数量。

看到了这题的标签滑动窗口,就想到了最朴素的方法。设windowSize = wordCount * wordLength,从头至尾移动窗口,检查窗口内的子串是否符合要求。

检查窗口内子串是否符合要求的做法是,使用Hash表,以单词为key,出现次数为value。例如第2个窗口“ordgoodgoodgoodb”,包含的单词为“ordg oodg oodg oodb”,对应表为“ordg:1, oodg:2, oodb:1”。同理把给定的words转换为Hash表,对比这两个表是否一致,一致则说明符合要求。代码如下:

C#

public class Solution
{
    public IList<int> FindSubstring(string s, string[] words)
    {
        int wordLength = words[0].Length;
        int wordCount = words.Length;
        int windowSize = wordCount * wordLength;
        if (s.Length < windowSize)
        {
            return [];
        }

        Dictionary<string, int> map = new();
        foreach (string word in words)
        {
            if (!map.TryAdd(word, 1))
            {
                map[word] += 1;
            }
        }

        List<int> result = [];
        for (int i = 0; i < s.Length - windowSize + 1; i++)
        {
            bool isValid = true;
            Dictionary<string, int> tempMap = new(map);
            for (int j = 0; j < wordCount; j++)
            {
                string fragment = s[(i + j * wordLength)..(i + j * wordLength + wordLength)];
                if (!tempMap.ContainsKey(fragment) || tempMap[fragment] == 0)
                {
                    isValid = false;
                    break;
                }

                tempMap[fragment] -= 1;
            }

            if (isValid)
            {
                result.Add(i);
            }
        }

        return result;
    }
}

但是时间复杂度太高了,虽然能过,但执行时间还是太长:

看了好多题解,都不是很明白,后来结合着想了想,优化了一下思路。上述解法是把滑动窗口瞄准了原始字符串,每次滑动只移动一个字符,但其实我们要判断的一个个的单词,移动一次窗口前后两个窗口之间并没有什么关联性,因此很难优化。若我们能每次窗口移动的是一个单词就好了,这样每次窗口移出去的是一个单词,新加入的也是一个单词,前后之间有重叠的部分,这样我们就可以进行优化了。

但是要怎么样确保移入移出的是一个单词呢?这里每个单词长度都是固定的,所以若考虑第1到wordLength个字符为一个单词、第wordLength+1到2*wordLength个字符为一个单词,依次类推,以上为一组划分;第2到wordLength+1个字符为一个单词、第wordLength+2到3*wordLength个字符为一个单词,依次类推,以上为一组划分……依次类推,一共可以分出wordLength组,如下图所示,每一行为一组划分:

这样一来,我们对每一组内的单词做滑动窗口,此时windowSize即为wordCount,接着逐一判断每个窗口是否符合要求即可。而此时,就不需要每个窗口重新创建一个新的Hash表了,只需要沿用上一个窗口的表,将已经移除出去的单词从统计表里减一、新加入的单词加入统计表里即可。代码如下:

C#

public class Solution
{
    public IList<int> FindSubstring(string s, string[] words)
    {
        int wordLength = words[0].Length;
        int wordCount = words.Length;
        if (s.Length < wordCount * wordLength)
        {
            return [];
        }

        Dictionary<string, int> map = new();
        foreach (string word in words)
        {
            if (!map.TryAdd(word, 1))
            {
                map[word] += 1;
            }
        }

        List<int> result = [];
        for (int i = 0; i < wordLength; i++)
        {
            int k = 0;
            Dictionary<string, int> tempMap = new(wordCount);
            for (int j = 0; j < s.Length / wordLength; j++)
            {
                if (i + j * wordLength + wordLength > s.Length)
                {
                    continue;
                }

                string fragment = s.Substring(i + j * wordLength, wordLength);
                if (!tempMap.TryAdd(fragment, 1))
                {
                    tempMap[fragment]++;
                }

                k++;

                if (k == wordCount + 1)
                {
                    k--;
                    string head = s.Substring(i + j * wordLength - k * wordLength, wordLength);
                    tempMap[head]--;
                    if (tempMap[head] == 0)
                    {
                        tempMap.Remove(head);
                    }
                }

                if (k == wordCount)
                {
                    bool isValid = true;
                    foreach (string key in map.Keys)
                    {
                        if (!tempMap.ContainsKey(key) || map[key] != tempMap[key])
                        {
                            isValid = false;
                            break;
                        }
                    }

                    if (isValid)
                    {
                        result.Add(i + (j - k + 1) * wordLength);
                    }
                }
            }
        }

        return result;
    }
}

执行用时大大减少: