StarryNights

每一个你所后悔的现在,都有一个不够努力的曾经

站内检索

循着字句,邂逅星光

搜索文章标题与正文,直达命中的章节

开始一次轻盈的站内探索

从这些主题开始探索

输入关键词

可同时输入多个词语缩小范围

发现内容

在摘要中查看高亮的关键词

快速直达

点击文章或章节标题继续阅读

# 引言

题目链接:https://leetcode.com/problems/substring-with-concatenation-of-all-words/

# 题目大意

给出一个长字符串s和一个字符串数组words, 返回由words中字符串拼接成的长字符串(假设为x)在s中的索引集合(x由words中所有元素拼接而成, 每一个元素都包含且只有一个)

Hint------------------
  1. words中的每个字符串长度相等

  2. words中的字符串可以重复(自己脑补了一波, 坑skr人T_T)

  • Example
Input:
  s = "barfoothefoobarman",
  words = ["foo","bar"]

Output: [0,9]

Input:
 s = "wordgoodgoodgoodbestword"
 words = ["word","good","best","good"]

Output: [8]

# 题解

这个题目其实和LeetCode的第三题Substring Without Repeating Characters的解题思路核心是一样的, 可以看看博主之前的题解 传送门 Click Here!都是利用滑动窗口的思想。

来分析下两道题题目的区别, 第三题求的是连续的最长的不包含重复字符的子串长度, 这道题目求解由words中字符串拼接成的长字符串x在s中的索引位置集合。转换一下, 就是s中查找连续的一段字符串x, 这个字符串由words中所有字符串拼接而成, 把所有符合条件的索引记录返回即可。 所以两者本质上是一致的。

由于本题words中所有字符串长度一致(假设为len), 所以可以把len当做一个整体。设定滑动窗口包含的子串由words中单词拼接而成(当前从某个起点开始正在查询并且不断扩展的子串), 采取如下步骤解题

  1. 申明一个hashmap, 用words中的字符串作为key在words中出现次数作为value, 构建比对映射表。
  2. 设定当前滑动窗口左边界lastDiffIndex, 初始为0
  3. 申明一个curhashmap表示已经匹配查询列表, 查询长度为len的子串sub在hashmap中是否存在
    1. 不存在:表明当前滑动窗口不符合规范, 不能由words中所有字符串凭拼接构成, 直接设置滑动窗口左边界为当前下标
    2. 存在:(1)curhashmap中的键值对对应数量小于hashmap查询子串有效, 滑动窗口左边界不变对应curhashmap对应键值数量加一, 继续查询下一个长度为len的子串sub, (2)curhashmap中的键值对对应数量大于等于hashmap查询子串, 无效, 移动滑动窗口左边界每次加len的跨度, 知道当前查询子串sub对应键值数量小于等于hashmap

这样可以做到每次跨度为words中单个字符串长度len, 为了能够遍历到所有单词组合, 一般来说会选择双层循环外层每次+1再次遍历, 但是本题words中字符串长度一致, 每次跨度都是len, 所以只需要+1往后偏移len-1次进行跨度为len的完全遍历即可覆盖s中的所有字符, 因为再次偏移到len实际上已经进入了下一个周期循环完全重复。

因此本题采用双层循环, 外层i = [0-len]跨度为1, 内层j = [i, lens-len]跨度为len, 每次截取长度为[j,j+len]之间的字符串判断, 总遍历次数 为 len*n/len=n

# 复杂度

时间复杂度 O(n)

空间复杂度 O(km), k为给出的单词序列的长度, m为序列中每个单词的长度

# AC代码

c++版本

class Solution
{
  public:
    vector<int> findSubstring(string s, vector<string> &words)
    {
        // invalid
        if (s.length() <= 0 || words.size() <= 0)
        {
            return vector<int>{};
        }
        unordered_map<string, int> hashMap;
        unordered_map<string, int> curhashxMap;
        vector<int> ret;
        for (string word : words)
        {
            ++hashMap[word];
        }
        int lenS = s.length();
        int lenWord = words[0].length();
        int lens = words.size();
        int lastDiffIndex = 0;
        int count = 0;
        for (int i = 0; i < lenWord; ++i)
        {
            curhashxMap.clear();
            lastDiffIndex = i;
            count = 0;
            for (int j = i; j <= lenS - lenWord; j += lenWord)
            {
                string tmp = s.substr(j, lenWord);
                if (hashMap.count(tmp) <= 0)
                {
                    curhashxMap.clear();
                    lastDiffIndex = j + lenWord;
                    count = 0;
                    continue;
                }
                ++curhashxMap[tmp];
                ++count;
                if (curhashxMap[tmp] <= hashMap[tmp])
                {
                    if (lens == count)
                    {
                        ret.push_back(lastDiffIndex);
                        --curhashxMap[s.substr(lastDiffIndex, lenWord)];
                        --count;
                        lastDiffIndex += lenWord;
                    }
                }
                else
                {
                    while (curhashxMap[tmp] > hashMap[tmp])
                    {
                        --curhashxMap[s.substr(lastDiffIndex, lenWord)];
                        --count;
                        lastDiffIndex += lenWord;
                    }
                }
            }
        }
        return ret;
    }
};

go版本

func findSubstring(s string, words []string) []int {
	lenS, lenWords := len(s), len(words)
	if 0 == lenS || 0 == lenWords {
		return nil
	}
	hashMap, curHashMap := make(map[string]int), make(map[string]int)
	var ret []int
	lenword := len(words[0])
	for _, v := range words {
		hashMap[v]++
	}
	for i := 0; i < lenword; i++ {
		curHashMap = make(map[string]int)
		lastDiffIndex, count := i, 0
		for j := i; j <= lenS-lenword; j += lenword {
			tmp := s[j : j+lenword]
			if _, ok := hashMap[tmp]; false == ok {
				curHashMap = make(map[string]int)
				lastDiffIndex, count = j+lenword, 0
				continue
			}
			curHashMap[tmp]++
			count++
			if curHashMap[tmp] <= hashMap[tmp] {
				if lenWords == count {
					ret = append(ret, lastDiffIndex)
					curHashMap[s[lastDiffIndex:lastDiffIndex+lenword]]--
					count--
					lastDiffIndex += lenword
				}
			} else {
				for curHashMap[tmp] > hashMap[tmp] {
					curHashMap[s[lastDiffIndex:lastDiffIndex+lenword]]--
					count--
					lastDiffIndex += lenword
				}
			}
		}
	}
	return ret
}
更新于 阅读次数 — 次
STARRY NIGHTSPERSONAL NOTES

赞赏

文章至此,故事还在继续。

Seiun

长按或右键保存收款码

文章关联

2 篇关联

顺着引用的连线,读见篇章交错的脉络

展开关系图

拖动平移 · Ctrl / ⌘ + 滚轮缩放 · 点击节点阅读使用 + / − 按钮缩放 · 放大视图后可拖动 · 点击节点阅读单指拖动平移 · 使用 + / − 按钮缩放 · 点击节点阅读

当前文章引用本文本文引用
引用列表

引用本文1

  • LeetCode Programming
    AI 摘要

    本文是作者为缓解拖延、练习 Go 语言而开启的 LeetCode 刷题记录,兼作个人题解。文中先介绍 C++ 输入输出加速技巧,通过 ios::sync_with_stdio(false) 解除 C/C++ 流同步、cin.tie(nullptr) 解除 cin 与 cout 绑定来提升效率;随后汇总多道 LeetCode 题解,列出题号、题目、难度及链接,涵盖第1至32题等。

本文引用1

  • LeetCode_3: Longest Substring Without Repeating Characters
    AI 摘要

    文章介绍 LeetCode 无重复字符最长子串问题:给定字符串,求不含重复字符的最长子串长度。题解采用哈希表记录每个字符上次出现的下标,并用滑动窗口维护当前无重复子串起点。遍历字符串时,若字符已出现且其上次位置加一大于当前起点,则更新起点;随后更新字符位置,计算当前子串长度并维护最大值。时间复杂度为 O(n),文末给出 C++ 与 Go 实现。

文章关联