leetcode 30. 串联所有单词的子串-java详细版本
题目所属分类
可以用字符串哈希做 这样的话复杂度会变成O(n)
原题链接
给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。
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] 也是可以的。
题解
- (重点)划分区间,因为每个word的长度是相同的,因此可以将s串划分为从第0个元素开始匹配、从第1个元素开始匹配、… 、从第w-1个元素开始匹配的w种情况,这样就能保证给定的words串不会横跨区间。
- 在上述基础上,用一个哈希表a存储给定的m个单词;用另一个哈希表b存储滑动窗口中的元素,每次滑动窗口时,只会在此哈希表中插入一个元素,再删除一个元素;(键值对为 ---- 单词 :单词出现的次数)
- 用cnt存储哈希表b中有多少个单词是哈希表a中的。如果窗口中出现过该单词,并且窗口中该单词的数量小于原单词组中的数量,则表示该单词有效,cnt++。最后,如果cnt = m,则得到一个答案。从而使得滑动窗口匹配子串时的时间复杂度降为O(1)。
这个写的好一些
应用了字符串哈希
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
// 存储答案
List<Integer> list = new ArrayList<Integer>();
// n为字符串长度,m为单词数量,w为单词长度
int n = s.length(),m = words.length,w = words[0].length();
// 将原来的单词存储在哈希表中,value值表示它有多少个
HashMap<String, Integer> map_words = new HashMap<String, Integer>();
for(String word : words) map_words.put(word,map_words.getOrDefault(word,0) + 1);
// 定义当前窗口的哈希表
HashMap<String, Integer> map_windows = new HashMap<String, Integer>();
// 划分s串,定义窗口的起始位置从0 到 n - m * w
for(int i = 0;i <= n - m * w;i ++)
{
// 每次读入窗口清空原来的窗口哈希表
map_windows.clear();
// cnt表示哈希表map_windows中有多少个单词是哈希表map_words中的
int cnt = 0;
// 划分单词m次
for(int j = 0;j < m;j ++)
{
// 将长度为w的单词划分出来
String t = s.substring(i + j * w,i + j * w + w);
// 如果原单词中存在t,则继续,否则直接break此次循环
if(map_words.containsKey(t))
{
// 将t存储在map_windows中
map_windows.put(t,map_windows.getOrDefault(t,0) + 1);
// 如果t在窗口中出现的次数大于t在原单词组中出现的次数,直接break此次循环
if(map_windows.get(t) > map_words.get(t)) break;
else cnt++;
}
else break;
}
if(cnt == m) list.add(i);
}
return list;
}
}
字符串哈希没用上
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<>();
if(words.length == 0) return res;
int n = s.length();int m = words.length ; int w = words[0].length();
HashMap<String, Integer> total = new HashMap<String, Integer>();
for(String word : words){
total.put(word,total.getOrDefault(word,0) + 1);
}
for(int i = 0 ; i < w ;i++){
// 因为每一个单词的长度是相等的 == w,所以只需要遍历起点 0--w - 1
//当前窗口的的哈希表
HashMap<String, Integer> find = new HashMap<String, Integer>();
int cnt = 0 ;// 统计窗口内单词在 words 中出现的次数
for(int j = i ; j + w <= n ; j += w ){
//窗口已经满,需要去掉窗口最左边的单词,才能在窗口中添加新的单词
if(j >= i + m * w){
String word = s.substring(j-m*w, w+j-m*w);
find.put(word,find.get(word)-1);//去除窗口最左边的单词
// 不加 == 是因为没有减去的时候是 == ,减去了之后是 <,那 word 肯定是需要的单词
//total.get(word) != null这个不加会报错
if (total.get(word) != null && find.get(word) < total.get(word))//说明减到的是一个有效单词
cnt--;
}
String word = s.substring(j, j+w);
find.put(word, find.getOrDefault(word, 0) + 1); //在窗口最右边添加新的单词
if (total.get(word) != null && find.get(word) <= total.get(word))
cnt++;
if (cnt == m)
res.add(j - (m-1)*w);//区间的起点
}
}
return res ;
}
}
总结
- 划分字符串的技巧
- 用哈希表存储单词以及单词出现的次数
- 用cnt来控制两个哈希表是否相同,如果窗口中出现过该单词,并且窗口中该单词的数量小于原单词组中的数量,则表示该单词有效,cnt++。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)