一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情。
题目描述
给定一个字符串 s 和一些 长度相同 的单词 words 。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符 ,但不需要考虑 words 中单词串联的顺序。
示例
输入:s = "barfoothefoobarman", words = ["foo","bar"]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。
输入: s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
输出: []
输入: s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
输出: [6,9,12]
提示
1 <= s.length <= 10s由小写英文字母组成1 <= words.length <= 50001 <= words[i].length <= 30words[i]由小写英文字母组成
回溯 + 滑动窗口
通过回溯的方式,我们可以将所有的单词进行一个排列组合,得到字符串集合。
再取得字符串的拼接长度,通过滑动窗口的方式,遍历字符串s,判断其字串是否存在字符串集合中。
class Solution {
private Set<String> set;
public List<Integer> findSubstring(String s, String[] words) {
// 保存拼接的字符串
set = new HashSet<>();
// 回溯
back(words, 0, "");
// 计算窗口长度
int len = 0;
for(String word : words){
len += word.length();
}
List<Integer> list = new ArrayList<>();
for(int i = 0; i + len <= s.length(); ++i){
// 判断当前字串是否匹配
if(set.contains(s.substring(i, i + len))){
list.add(i);
}
}
return list;
}
/**
* 回溯
* @param words 字符串数组
* @param idx 指针
* @param str 前置拼接的字符串
*/
private void back(String[] words, int idx, String str){
if(idx == words.length){
set.add(str);
return;
}
for(int i = idx; i < words.length; ++i){
if(i != idx && words[i].equals(words[idx])){
continue;
}
swap(words, idx, i);
back(words, idx + 1, str + words[idx]);
swap(words, idx, i);
}
}
/**
* 交换
* @param i 指针
* @param j 指针
*/
private void swap(String[] words, int i, int j){
String tmp = words[i];
words[i] = words[j];
words[j] = tmp;
}
}
回溯算法的时间复杂度是: ,在数据量小的时候进行组合排列还可以得到结果,但是一旦数据量上去了,其所需的时间会增长很多,导致内存溢出或超时,因此该解法不适用于当前的题目。
哈希表
由于回溯排序会超时,那我们尝试着使用计数法来判断下。将words中出现的单词次数统计起来,再遍历字符串s,通过计算出来的区间,滑动统计子字符串中的单词出现次数是否与words中单词出现的次数匹配。如果当前子字符串中单词出现的次数超过words中统计的数量,则表示该排列不匹配。
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
// 统计单词出现的次数
Map<String, Integer> wordMap = new HashMap<>();
for(String word : words){
wordMap.put(word, wordMap.getOrDefault(word, 0) + 1);
}
// 窗口长度
int n = words[0].length(), wordLen = n * words.length;
List<Integer> res = new ArrayList<>();
int idx = 0;
while(idx + wordLen <= s.length()){
// 统计子字符串中单词出现次数
Map<String, Integer> map = new HashMap<>();
boolean flag = true;
for(int i = idx; i <= idx + wordLen - n; i += n){
String word = s.substring(i, i + n);
map.put(word, map.getOrDefault(word, 0) + 1);
// 如果单词出现的次数超过字符串数组中出现的次数,则表示不匹配
if(wordMap.getOrDefault(word, 0) < map.get(word)){
flag = false;
break;
}
}
if(flag){
res.add(idx);
}
++idx;
}
return res;
}
}