Open azl397985856 opened 2 years ago
注意点:
需要队word浅拷贝一份, 因为每次都会删除数组一个索引
var findSubstring = function(s, words) {
const len = words[0].length
const res = []
// 循环, 循环的次数为字符串长度减去words所有字符串总和长度-1, 因为这个长度后面的字符串肯定不能拼接成words数组的数据
for (let i = 0; i <= s.length - words.length * len; i++) {
const wordsCopy = [...words]
// 深度优先遍历
dfs(wordsCopy, s.substring(i), i)
}
return res
function dfs(arr, s, start) {
// 递归的结束条件为数组的长度为0, 或者进不去下方的判断
if (arr.length === 0) return res.push(start)
// 从字符串开始剪切固定长度字符串, 去words中查找, 如果找不到, 结束, 如果找到了 继续往下查找
const str = s.substr(0, len)
const index = arr.findIndex((item) => item === str)
if (index > -1) {
// 递归查找之前需要将已经使用过的数组索引删除, 字符串也需要删除已经判断过的
arr.splice(index, 1)
dfs(arr, s.substring(len), start)
}
}
};
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
Map<String, Integer> allWords = new HashMap<>();
for(String word : words){
allWords.put(word, allWords.getOrDefault(word, 0) + 1);
}
int wordNum = words.length, wordLen = words[0].length();
List<Integer> res = new ArrayList<>();
for(int i = 0; i < s.length() - wordNum * wordLen + 1; i++){
Map<String, Integer> subWords = new HashMap<>();
int index = i;
while(index < i + wordNum * wordLen){
String curWord = s.substring(index, index + wordLen);
if(!allWords.containsKey(curWord) || subWords.get(curWord) == allWords.get(curWord)){
break;
}
subWords.put(curWord, subWords.getOrDefault(curWord, 0) + 1);
index += wordLen;
}
if(index == i + wordNum * wordLen){
res.add(i);
}
}
return res;
}
}
30. 串联所有单词的子串
https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words/
滑动窗口!
我们一直在 s 维护着所有单词长度总和的一个长度队列!
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
from collections import Counter
if not s or not words:return []
one_word = len(words[0])
word_num = len(words)
n = len(s)
words = Counter(words)
res = []
for i in range(0, one_word):
cur_cnt = 0
left = i
right = i
cur_Counter = Counter()
while right + one_word <= n:
w = s[right:right + one_word]
right += one_word
cur_Counter[w] += 1
cur_cnt += 1
while cur_Counter[w] > words[w]:
left_w = s[left:left+one_word]
left += one_word
cur_Counter[left_w] -= 1
cur_cnt -= 1
if cur_cnt == word_num :
res.append(left)
return res
思路: 暴力解法,使用map保存,循环判断每一个词是否符合 双层循环,(半天只看懂了这个..)
var findSubstring = function (s, words) {
if (!words || !words.length) return [];
let wordLen = words[0].length;
// 计算所有字串的长度 因为内部字串的长度都相同.直接用一个字串的长度乘以语所有字串的数量
let allWordsLen = wordLen * words.length;
let ans = []
// 统计字符出现的次数
let wordMap = {};
for (let w of words) {
wordMap[w] ? wordMap[w]++ : wordMap[w] = 1
}
// 循环次数为总长度减去字符长度+1 因为再往后查找,肯定没有符合的字符
for (let i = 0; i < s.length - allWordsLen + 1; i++) {
let wm = Object.assign({}, wordMap);
for (let j = i; j < i + allWordsLen - wordLen + 1; j += wordLen) {
// 拆分字符.查看是否符合要求
let w = s.slice(j, j + wordLen);
if (wm[w]) {
wm[w]--
} else {
break;
}
}
if (Object.values(wm).every(n => n === 0)) ans.push(i);
}
return ans;
};
时间复杂度:O(NM) s字符长度N wordsM 空间复杂度:O(N)
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
from collections import Counter
if not s or not words:return []
all_len = sum(map(len, words))
n = len(s)
words = Counter(words)
res = []
for i in range(0, n - all_len + 1):
tmp = s[i:i+all_len]
flag = True
for key in words:
if words[key] != tmp.count(key):
flag = False
break
if flag:res.append(i)
return res
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
int n = words[0].length();
int sumLength = n * words.length;
Map<String, Integer> map = new HashMap<>();
for (String word : words) {
map.put(word, map.getOrDefault(word, 0) + 1);
}
List<Integer> res = new ArrayList<>();
for (int i = 0; i < s.length() - sumLength + 1; i++) {
HashMap<String, Integer> tempMap = new HashMap<>();
String cur = s.substring(i, i + sumLength);
for (int j = 0; j < sumLength; j += n) {
String s1 = cur.substring(j, j + n);
if(!map.containsKey(s1)) {
break;
}
tempMap.put(s1, tempMap.getOrDefault(s1, 0) + 1);
if(map.get(s1) < tempMap.get(s1)) {
break;
}
if(j + n == sumLength) {
res.add(i);
}
}
}
return res;
}
}
from collections import Counter
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
if not s or not words: return []
res = []
n = len(words)
word_len = len(words[0])
window_len = word_len * n
target = Counter(words)
i = 0
while i < len(s) - window_len + 1:
sliced = []
start = i
for _ in range(n):
sliced.append(s[start:start + word_len])
start += word_len
if Counter(sliced) == target:
res.append(i)
i += 1
return res
哈希表、滑动窗口
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
words_length = len(words)*len(words[0])
words_dict = collections.Counter(words)
ans = []
for i in range(0, len(s)-words_length+1):
temp = s[i:i+words_length]
temp_list = [temp[j:j+len(words[0])] for j in range(len(temp)) if j % len(words[0]) == 0]
temp_dict = collections.Counter(temp_list)
if words_dict == temp_dict:
ans.append(i)
return ans
(mark 先抄作业)
const findSubstring = (s, words) => {
if (!words || words.length === 0) return [];
const m = words.length,
n = words[0].length,
len = m * n,
result = [];
// Build the word-count hash map
const map = {};
for (word of words) map[word] = ~~map[word] + 1;
// Try every possible start position i
for (let i = 0; i < s.length - len + 1; i++) {
// Make a copy of the hash map
const temp = Object.assign({}, map);
for (let j = i; j < i + len; j += n) {
const str = s.substr(j, n);
// Cannot find the word in hash map (words list), try another position
if (!(str in temp)) break;
// All the same word str are found, remove it from the hash map
if (--temp[str] === 0) delete temp[str];
}
// We have gone through the whole s and used all our words in the list
if (Object.keys(temp).length === 0) result.push(i);
}
return result;
};
思路:哈希表+滑动窗口
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<>();
HashMap<String,Integer> map1 = new HashMap<>();
//定义单个word长度,个数,并且自然得到子串的长度
int oneWord = words[0].length();
int wordNum = words.length;
int len = oneWord * wordNum;
for(String word :words){
//存储单词各个word的次数: key为单词,value为单词出现次数
map1.put(word,map1.getOrDefault(word,0) + 1);
}
for(int i = 0; i < s.length() - len + 1; i++){
//循环截取所有串联字符串
//substring(x, y):x, y分别为开始/结束位置,左闭右开,[ )
String tmp = s.substring(i,i + len);
HashMap<String,Integer> map2 = new HashMap<>();
//所有给定长度的字符串,按照单个word长度拆分
//截取字符串出现次数,key为单词,value为单词出现次数
for(int j = 0; j < len; j += oneWord){
String w = tmp.substring(j, j +oneWord);
map2.put(w,map2.getOrDefault(w, 0) + 1);
}
if(map1.equals(map2)){
res.add(i);
}
}
return res;
}
}
时间复杂度:O(MN) 空间复杂度:O(N)
typedef struct myStruct {
char *key;
int cnt; /* 单词表中key单词出现的次数 */
int usedCnt; /* 子串中已经被使用的次数,由于顺序无所谓,所以只统计次数是否用完即可 */
UT_hash_handle hh;
} WordNode;
WordNode *map;
void Init(char ** words, int wordsSize) /* 创建单词的hash表 */
{
map = NULL;
WordNode *s = NULL;
int len = strlen(words[0]);
for (int i = 0; i < wordsSize; i++) {
HASH_FIND_STR(map, words[i], s);
if (s == NULL) {
s = calloc(1, sizeof(WordNode));
s->key = calloc(len + 1, sizeof(char));
strcpy(s->key, words[i]);
s->cnt++;
HASH_ADD_KEYPTR(hh, map, s->key, len, s);
} else {
s->cnt++;
}
}
}
void ResetMap()
{
WordNode *s, *tmp;
HASH_ITER(hh, map, s, tmp) {
s->usedCnt = 0;
}
}
int* findSubstring(char * s, char ** words, int wordsSize, int* returnSize)
{
if ((s == NULL) || (words == NULL) || (wordsSize == 0)) {
*returnSize = 0;
return NULL;
}
Init(words, wordsSize);
int len = strlen(s);
int *res = calloc(len, sizeof(int));
int cnt = 0;
int wordLen = strlen(words[0]);
char *tmp = calloc(wordLen + 1, sizeof(char));
WordNode *t;
for (int i = 0; i <= len - (wordLen * wordsSize); i++) { /* 每个字母进行向后移位 */
int j;
ResetMap(); /* 重置map中的used计数 */
for (j = i; j < i + (wordLen * wordsSize); j += wordLen) { /* 当前从i开始,子串分割成wordSize个单词,查找hash表 */
strncpy(tmp, &s[j], wordLen);
HASH_FIND_STR(map, tmp, t);
if ((t == NULL) || (t->usedCnt == t->cnt)) { /* 如果找不到,或者使用次数已经超过单词表中存在的数量,break */
break;
} else {
t->usedCnt++; /* 使用次数+1 */
}
}
if (j >= i + (wordLen * wordsSize)) { /* 所有单词都能匹配上 */
res[cnt++] = i;
}
}
/* 这里需要用HASH_ITER释放hash表,提交时忘了,但也能过,懒得加上了 */
*returnSize = cnt;
return res;
}
【思路—哈希表+双指针】
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> ans;
unordered_map<string, int> map;
if(words.size() == 0)
return ans;
for(auto word : words)
map[word]++;
int sLen = s.size(), wLen = words[0].size(), wCnt = words.size();
int match = 0;
for(int i = 0; i < sLen - wLen * wCnt + 1; i++){
string cur = s.substr(i, wLen * wCnt);
unordered_map<string, int> tmp_map;
int j = 0;
for(; j < cur.size(); j += wLen) {
string word = cur.substr(j, wLen);
if(map.find(word) == map.end())
break;
tmp_map[word]++;
if(tmp_map[word] > map[word])
break;
}
if(j == cur.size())
ans.push_back(i);
}
return ans;
}
时间复杂度:$O(n\cdot m \cdot k)$,其中n 为字符串 S 长度, m 为 words 数组元素个数, k 为单个 word 字串长度;
空间复杂度:$O(m)$。
遍历s,每次取words中单词串联形成的子串长度,比较子串中单词出现次数与words是否相同。
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
hashset = {}
for w in words:
hashset[w] = hashset.setdefault(w, 0) + 1
l = len(words)
wl = len(words[0])
ans = []
for i in range(len(s)-wl*l+1):
tmp = {}
for j in range(i, i+wl*l, wl):
tmp[s[j:j+wl]] = tmp.setdefault(s[j:j+wl], 0) + 1
if tmp == hashset:
ans.append(i)
return ans
复杂度
https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words/
给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
示例 1:
输入:
s = "barfoothefoobarman",
words = ["foo","bar"]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。
示例 2:
输入:
s = "wordgoodgoodgoodbestword",
words = ["word","good","best","word"]
输出:[]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
naive 的想法是,将 words 里面的单词组合成字符串,然后到 s 里面去找有没有跟它一样的子串。但是,这样子时间复杂度太高了,因为 words 的组合有 words.length!
种,总复杂度差不多就是 s.length * words.length!
了。
那我们反过来想,s 的子串最多只有 s.length
个,那我们只要判断 s 的每个子串是不是刚好由 words 数组里面的所有单词组成的就可以了。所以说,如果子串是刚好匹配 words 的单词的话,说明子串和 words 组成的字符串是两种组合情况,虽然排序不同,但包含的单词种类和数量是一样的。
所以解决方法就很明显了:
JavaScript Code
/**
* @param {string} s
* @param {string[]} words
* @return {number[]}
*/
var findSubstring = function(s, words) {
const wordLen = words[0].length;
const substrLen = wordLen * words.length;
const initialWordsMap = words.reduce((map, w) => {
map[w] = (map[w] || 0) + 1;
return map;
}, {})
const res = [];
for (let i = 0; i <= s.length - substrLen; i++) {
const wordsMap = {...initialWordsMap};
for (let j = i; j < i + substrLen; j += wordLen) {
const word = s.slice(j, j + wordLen);
if (!(word in wordsMap) || wordsMap[word] == 0) break;
wordsMap[word]--;
}
if (usedUpWords(wordsMap)) res.push(i);
}
return res;
// ******************************************
function usedUpWords(map) {
return Object.values(map).every(n => n == 0);
}
};
/**
* @param {string} s
* @param {string[]} words
* @return {number[]}
*/
var findSubstring = function (s, words) {
const wordSize = words[0].length;
const substringLen = wordSize * words.length;
const wordsCount = {};
words.forEach(w => (wordsCount[w] = (wordsCount[w] || 0) + 1));
const res = [];
for (let i = 0; i <= s.length - substringLen; i++) {
const tempCount = { ...wordsCount };
let count = words.length;
for (let j = i; j < i + substringLen; j += wordSize) {
const word = s.slice(j, j + wordSize);
if (!(word in tempCount) || tempCount[word] <= 0) break;
tempCount[word]--;
count--;
}
if (count === 0) res.push(i);
}
return res;
};
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> ans={};
//len将是我们后续“窗口”移动的步长
int len=words[0].size();
//若s的长度比“窗口”的长度还要短,输出{}
if(len>s.size()) return ans;
//使用哈希表记录words里面元素的个数,两个作用:1)判断words中是否存在某子串;2)判断words中某子串的个数
unordered_map<string,int> mymap;
for(int i=0;i<words.size();i++)
mymap[words[i]]++;
//遍历字符串s
for(int i=0;i<=s.size()-len;i++){
//若以i开头的子串不存在于哈希表中,也就无需后续比较,直接continue
if(mymap.find(s.substr(i,len))==mymap.end()) continue;
//若存在:
//由于mymap和i后续还要用到,不能对其进行更改,故定义两个临时变量:哈希表tmp、整型j
unordered_map<string,int> tmp=mymap;
int j=i;
//对每一个“窗口”进行判断,若该窗口子串存在且其个数大于0,将其个数减1,窗口向后移动一个步长,否则退出循环
for(int count=words.size();count>0;count--){
string a=s.substr(j,len);
if(tmp.find(a)==tmp.end() || tmp[a]==0) break;
else{
j+=len;
tmp[a]--;
}
//若直到进行了 words.size() 个窗口的判断子串都存在,匹配成功,将 i 的值放入ans
if(j==i+len*words.size()) ans.push_back(i);
}
}
return ans;
}
};
/**
* @param {string} s
* @param {string[]} words
* @return {number[]}
*/
var findSubstring = function (s, words) {
const len = words[0].length
const res = []
for (let i = 0; i <= s.length - words.length * len; i++) {
const wordsCopy = [...words]
dfs(wordsCopy, s.substring(i), i)
}
return res
function dfs(arr, s, start) {
if (arr.length === 0) return res.push(start)
const str = s.substr(0, len)
const index = arr.findIndex((item) => item === str)
if (index > -1) {
arr.splice(index, 1)
dfs(arr, s.substring(len), start)
}
}
}
思路:(暴力法),从字符串当前位置进行遍历,判断能够构成words,若不构成则移动到下一个位置。
class Solution { public List<Integer> findSubstring(String s, String[] words) { List<Integer> list = new ArrayList<>(); if(words==null||words.length==0)return list; int size= words[0].length(); HashMap<String,Integer> map=new HashMap<>(); for (String temp:words){ map.put(temp,map.getOrDefault(temp,0)+1); } for(int i=0;i+size<=s.length();i+=1){ if(!map.containsKey(s.substring(i,i+size)))continue; int j=i; HashMap<String,Integer> map_t=new HashMap<>(map); while (j+size<=s.length()&&!map_t.isEmpty()){ String temp=s.substring(j,j+size); if(!map_t.containsKey(temp)){ break; }else{ int t=map_t.get(temp); if(t==1){ map_t.remove(temp); }else{ map_t.put(temp,t-1); } } j+=size; } if(map_t.isEmpty()){ list.add(i); } } return list; } }
先mark一下,今天太忙,明儿再看
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();
if (words == null || words.length == 0)
return res;
for (String word : words)
map.put(word, map.getOrDefault(word, 0) + 1);
int sLen = s.length(), wordLen = words[0].length(), count = words.length;
int match = 0;
for (int i = 0; i < sLen - wordLen * count + 1; i++) {
//得到当前窗口字符串
String cur = s.substring(i, i + wordLen * count);
Map<String, Integer> temp = new HashMap<>();
int j = 0;
for (; j < cur.length(); j += wordLen) {
String word = cur.substring(j, j + wordLen);
// 剪枝
if (!map.containsKey(word))
break;
temp.put(word, temp.getOrDefault(word, 0) + 1);
// 剪枝
if (temp.get(word) > map.get(word))
break;
}
if (j == cur.length())
res.add(i);
}
return res;
}
}
Hash表+双指针
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
word_len = len(words[0])
words_length = len(words) * word_len
ret = []
if not s or not words or len(s) < words_length:
return ret
l = 0
hash_words = Counter(words)
while l <= len(s) - words_length:
child_words = [s[i:i + word_len] for i in range(l, l + words_length, word_len)]
if hash_words == Counter(child_words):
ret.append(l)
l += 1
return ret
class Solution {
public List
JavaScript Code:
/**
* @param {string} s
* @param {string[]} words
* @return {number[]}
*/
var findSubstring = function (s, words) {
const len = words[0].length
const res = []
for (let i = 0; i <= s.length - words.length * len; i++) {
const wordsCopy = [...words]
dfs(wordsCopy, s.substring(i), i)
}
return res
function dfs(arr, s, start) {
if (arr.length === 0) return res.push(start)
const str = s.substr(0, len)
const index = arr.findIndex((item) => item === str)
if (index > -1) {
arr.splice(index, 1)
dfs(arr, s.substring(len), start)
}
}
}
复杂度分析
令 n 为数组长度。
先用哈希表统计 w 中每个单词的词频
然后每次取 word 中长度相同的一段, 分割成 num_word 的小段进行匹配
如果 这个单词 不在哈希表中或者超出原来的数目, 那么就返回
否则将这个 index 加入 result
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
d = collections.defaultdict(int)
for w in words:
d[w] += 1
len_word = len(words[0])
num_words = len(words)
len_s = len(s)
res = []
for i in range(0, len_s - len_word * num_words+1):
part_word = s[i:i+len_word * num_words]
part_words = [part_word[x*len_word:(x+1)*len_word] for x in range(num_words)]
d1 = collections.defaultdict(int)
is_substring = True
for w in part_words:
if w not in d:
is_substring = False
break
else:
d1[w] += 1
if d1[w] > d[w]:
is_substring = False
break
if is_substring:
res.append(i)
return res
N 为 s 长度, m 为 word 数组元素个数, n 为 word 数组单词长度
时间复杂度: O(Nm) 外面一层循环遍历 N 为 s 的长度, 里面的循环遍历个 里面的单词, 数目 为 m 个
空间复杂度: O(m) 哈希表的复杂度是 O(m)
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> res;
// 设 words中所有单词的长度为 d
int n = s.size(), m = words.size(), d = words[0].size();
int len = 0;
unordered_map<string, int> um;
for (string w : words) {
len += w.size();
um[w]++;
}
// init: 初始化长度为 d 的数组
vector<unordered_map<string, int> > vu(d);
for (int i = 0; i < d && i + len <= n; i++) {
for (int j = i; j < i + len; j += d) {
string w = s.substr(j, d);
vu[i][w]++;
}
if (vu[i] == um) {
res.emplace_back(i);
}
}
// sliding window: 滑动窗口,每次移动 d 个位置
for (int i = d; i + len <= n; i++) {
int r = i % d;
string wa = s.substr(i - d, d), wb = s.substr(i + len - d, d);
if(--vu[r][wa] == 0) vu[r].erase(wa);
vu[r][wb]++;
if (vu[r] == um) {
res.emplace_back(i);
}
}
return res;
}
};
不是很理解,先打打卡,正在啃,,,
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> ret;
if(words.size() == 0)//判断words为空,因为下面用到了words[0]
return ret;
int word_size = words[0].size();
int word_num = words.size();
unordered_map<string,int> m1;//构造hashmap
for(int i=0;i<word_num;i++)
m1[words[i]]++;
unordered_map<string,int> m2;
for(int i=0; (i + word_size * word_num) <= s.size() ; i++){//截取的s符合题意
int j = 0;
for(j=i;j < (i + word_size * word_num) ; j=j+word_size){//分段判断
string temp_str = s.substr(j,word_size);
if(m1[temp_str] == 0){//m1中没有截取的串,直接跳出
break;
}else{
m2[temp_str]++;
if(m1[temp_str] < m2[temp_str])//重复次数过多,也跳出
break;
}
}
if(j == (i + word_size * word_num))//每一段都符合,则加入答案
ret.push_back(i);
m2.clear();//清空m2
}
return ret;
}
};
时间复杂度:O(s.length()* words.length)
空间复杂度: O(words.length)
java
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
Map<String, Integer> allWords = new HashMap<>();
for(String word : words){
allWords.put(word, allWords.getOrDefault(word, 0) + 1);
}
int wordNum = words.length, wordLen = words[0].length();
List<Integer> res = new ArrayList<>();
for(int i = 0; i < s.length() - wordNum * wordLen + 1; i++){
Map<String, Integer> subWords = new HashMap<>();
int index = i;
while(index < i + wordNum * wordLen){
String curWord = s.substring(index, index + wordLen);
if(!allWords.containsKey(curWord) || subWords.get(curWord) == allWords.get(curWord)){
break;
}
subWords.put(curWord, subWords.getOrDefault(curWord, 0) + 1);
index += wordLen;
}
if(index == i + wordNum * wordLen){
res.add(i);
}
}
return res;
}
}
先遍历一遍输入的words
建立词频哈希。再遍历每个长度可能符合要求的子串,因为words
中单词长度相同,所以在每个点截词去之前建立的词频哈希中比对,能完成一一对应的子串即为所求子串
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
map<string, int> mp;
vector<int> res;
int cnt = 0;
for (string word : words) {
++mp[word];
++cnt;
}
int wordLen = words[0].size();
int ss = wordLen * cnt;
const size_t len = s.size();
for (int i = 0; i < len - ss + 1; ++i) {
string tmp = s.substr(i, ss);
map<string, int> tmpMp(mp);
bool flag = true;
for (int index = 0; index < ss; index += wordLen) {
if (!tmpMp[tmp.substr(index, wordLen)]) {
flag = false;
break;
}
--tmpMp[tmp.substr(index, wordLen)];
}
if (flag) {
res.push_back(i);
}
}
return res;
}
};
时间O(m)——m为s长度
空间O(n)——n为words.length
var findSubstring = function (s, words) {
let left = 0, right = 0;
let slen = s.length;
let wordLen = words[0].length;
let wordNum = words.length;
let wlen = wordNum * wordLen;
let wordMap = new Map();
for (let word of words) {
let count = wordMap.has(word) ? wordMap.get(word) : 0;
wordMap.set(word, count + 1);
}
let res = [];
while (right < slen) {
right++;
if (right - left === wlen) {
if (match(s.substring(left, right), wordMap, wordNum, wordLen)) {
res.push(left);
}
left++;
}
}
return res;
};
function match(str, wordMap, wordNum, wordLen) {
let map = new Map();
for (let i = 0; i < wordNum; i++) {
let word = str.substring(i * wordLen, (i + 1) * wordLen);
let count = map.has(word) ? map.get(word) : 0;
map.set(word, count + 1);
}
let matchflag = true;
for (let [key, value] of wordMap) {
if (!map.has(key) || map.get(key) !== value) {
matchflag = false;
}
}
return matchflag;
}
java
class Solution {
public List
var findSubstring = function (s, words) {
let left = 0, right = 0;
let slen = s.length;
let wordLen = words[0].length;
let wordNum = words.length;
let wlen = wordNum * wordLen;
let wordMap = new Map();
for (let word of words) {
let count = wordMap.has(word) ? wordMap.get(word) : 0;
wordMap.set(word, count + 1);
}
let res = [];
while (right < slen) {
right++;
if (right - left === wlen) {
if (match(s.substring(left, right), wordMap, wordNum, wordLen)) {
res.push(left);
}
left++;
}
}
return res;
};
function match(str, wordMap, wordNum, wordLen) {
let map = new Map();
for (let i = 0; i < wordNum; i++) {
let word = str.substring(i * wordLen, (i + 1) * wordLen);
let count = map.has(word) ? map.get(word) : 0;
map.set(word, count + 1);
}
let matchflag = true;
for (let [key, value] of wordMap) {
if (!map.has(key) || map.get(key) !== value) {
matchflag = false;
}
}
return matchflag;
}
/**
* @param {string} s
* @param {string[]} words
* @return {number[]}
*/
var findSubstring = function (s, words) {
// 一个单词的长度
var oneWordLen = words[0].length;
// 所有单词总长度
var strLen = words.length * oneWordLen;
var wordsMap = {}
var res = []
// 存单词以及出现的次数
words.forEach(w => wordsMap[w] = (wordsMap[w] || 0) + 1)
// i从0开始, 在 区间【i,strLen+i】 里 判断 截取单词出来 与 map 里的数据进行消除
// 外层跳出循环判断优化 i < s.length - strLen + 1
for (let i = 0; i < s.length - strLen +1; i++) {
let map = { ...wordsMap }
let count = words.length
for (let j = i; j < strLen + j; j += oneWordLen) {
let str = s.substring(j, oneWordLen + j)
// 这里跳出此次的清除操作 在 某一个区间里的 str 不存在于map 里 跳出
if (map[str] === undefined || map[str] === 0)break
count--
map[str]--
}
// count 为 0 清除完毕 结果正确
if (count === 0) res.push(i)
}
return res
};
C++ Code:
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
unordered_map<string, int> allWordsMap;
for (auto& v : words) {
++allWordsMap[v];
}
int num = words.size();
int onelen = words[0].length();
vector<int> res;
if (s.length() < num * onelen) {
return res;
}
for (int left = 0; left < s.length() - num * onelen + 1; ++left) {
unordered_map<string, int> nowWordsMap;
int right = left;
while (right < left + num * onelen) {
auto cur = s.substr(right, onelen);
if (allWordsMap.find(cur) == allWordsMap.end()
|| nowWordsMap[cur] == allWordsMap[cur]) {
break;
}
++nowWordsMap[cur];
right += onelen;
}
if (right == left + num * onelen) {
res.emplace_back(left);
}
}
return res;
}
};
使用滑动窗口 + 双 Map
/**
* @param {string} s
* @param {string[]} words
* @return {number[]}
*/
var findSubstring = function (s, words) {
if (!s || !words || !words.length) return []
const wordsMap = new Map()
for (const word of words) wordsMap.set(word, (wordsMap.get(word) || 0) + 1)
const firstWordLen = words[0].length
const allWordsLen = firstWordLen * words.length
if (s.length < allWordsLen) return []
const res = []
for (let i = 0; i < firstWordLen; i++) {
let left = i, right = i
let count = 0, tmpMap = new Map()
while (right + firstWordLen <= s.length) {
const word = s.substring(right, right + firstWordLen)
right += firstWordLen
if (wordsMap.has(word)) {
tmpMap.set(word, (tmpMap.get(word) || 0) + 1)
count++
while (tmpMap.get(word) > wordsMap.get(word)) {
let tmpWord = s.substring(left, left + firstWordLen)
count--
tmpMap.set(tmpWord, (tmpMap.get(tmpWord) || 0) - 1)
left += firstWordLen
}
(count == words.length) && res.push(left)
} else {
left = right
tmpMap.clear()
count = 0
}
}
}
return res
}
时间复杂度: O(n), n = s.length
空间复杂度: O(m), m = words.length
双滑动窗口+哈希表
JavaScript Code
/**
* @param {string} s
* @param {string[]} words
* @return {number[]}
*/
var findSubstring = function(s, words) {
const len = words[0].length;
const totalLen = len * words.length;
if (s.length < totalLen) return [];
// 根据words生成单词出现频率的哈希表
const map = new Map();
for (const word of words) {
if (map.has(word)) {
map.set(word, map.get(word) + 1);
} else {
map.set(word, 1);
}
}
const res = [];
let left = 0;
while (left < s.length - totalLen + 1) {
const window = s.slice(left, left + totalLen), tempMap = new Map(map);
let right = 0;
while (right < totalLen) {
let temp = window.slice(right, right + len);
if (tempMap.get(temp)) {
tempMap.set(temp, tempMap.get(temp) - 1);
}
if (!Array.from(tempMap.values()).some(i => i !== 0)) {
res.push(left);
break;
}
right += len;
}
left++;
}
return res;
};
时间复杂度:O(n * l),l为words长度
空间复杂度:O(n)
class Solution {
public:
vector<int> res;
vector<int> findSubstring(string s_, vector<string>& words) {
// make hash
unordered_map<string,int> hash;
int w = words[0].size();
for(string& word: words){
if(hash.find(word)==hash.end())
hash[word] = hash.size();
}
int dict[hash.size()]; memset(dict,0,hash.size()*sizeof(int));
int note[hash.size()];
for(string& word: words)
dict[hash[word]]++;
// make hash version s
vector<int> s(s_.size(),-1);
string buffer(w, ' ');
for(int i=0;i<s.size()-w+1;i++){
memcpy(&buffer[0],&s_[i],w);
if(hash.find(buffer)!=hash.end())
s[i] = hash[buffer];
}
// delete unuseful slice
for(int i=0;i<w;i++){
for(int j=i;j<s.size()-w+1;j+=w){
if(s[j]==-1) continue;
int start = j;
while(j<s.size()-w+1&&s[j]!=-1)
j+=w;
if( (j-start) / w >= words.size())
continue;
else
for(int k=start;k<j;k+=w)
s[k] = -1;
}
}
// start double pointer
for(int i=0;i<w;i++){
for(int j=i;j<s.size()-w+1;j+=w){
if(s[j]==-1) continue;
// find a start position and clear the buffer
memset(note,0,hash.size()*sizeof(int));
int st = j, ed = j;
while(ed<s.size()-w+1 && s[ed]!=-1) {
int indexEd = s[ed];
if( ++note[indexEd] > dict[ indexEd ]){
while(note[indexEd] > dict[indexEd]){
int indexSt = s[st];
note[indexSt]--;
st+=w;
}
}
if( (ed - st)/w +1 == words.size())
res.push_back(st);
ed += w;
}
j = ed;
}
}
return res;
}
};
哈希表 + 双指针
/**
* @param {string} s
* @param {string[]} words
* @return {number[]}
*/
var findSubstring = function(s, words) {
if (!words || !words.length) return[];
let wordLen = words[0].length;
let allWordsLen = wordLen * words.length;
let ans = [], wordMap = {};
for (let w of words) {
wordMap[w] ? wordMap[w]++ :wordMap[w] = 1
}
for (let i = 0; i < s.length - allWordsLen + 1; i++) {
let wm = Object.assign({}, wordMap);
for (let j = i; j < i + allWordsLen - wordLen + 1; j += wordLen) {
let w = s.slice(j, j + wordLen);
if (wm[w]) {
wm[w]--
} else {
break;
}
}
if (Object.values(wm).every(n => n === 0)) ans.push(i);
}
return ans;
};
allWords
用于记录 words
中单词出现的次数,subWords
用于记录子串中(也就是滑动窗口中)单词出现的次数;wordNum
为单词的个数,wordLen
为单词长度;wordNum * wordLen
的滑动窗口,再在当前滑动窗口中依次比较 wordLen
长度的单词;allWords
中的单词,或者这个单词在子串中出现的次数已经等于 allWords
中的次数(也就是再加入这个子串次数就要超出了),这个滑动窗口就不符合要求,直接 break 进入下一个滑动窗口的匹配;res
中。class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
allWords = collections.Counter(words)
wordNum = len(words)
wordLen = len(words[0])
res = []
for i in range(len(s) - wordNum * wordLen + 1):
subWords = collections.defaultdict(int)
index = i
while index < i + wordNum * wordLen:
curWord = s[index: index + wordLen]
if curWord not in allWords or subWords[curWord] == allWords[curWord]:
break
subWords[curWord] += 1
index += wordLen
if index == i + wordNum * wordLen:
res.append(i)
return res
滑动窗口
代码
class Solution: def findSubstring(self, s: str, words: List[str]) -> List[int]: allWords = collections.Counter(words) wordNum = len(words) wordLen = len(words[0]) res = [] for i in range(len(s) - wordNum * wordLen + 1): subWords = collections.defaultdict(int) index = i while index < i + wordNum * wordLen: curWord = s[index: index + wordLen] if curWord not in allWords or subWords[curWord] == allWords[curWord]: break subWords[curWord] += 1 index += wordLen if index == i + wordNum * wordLen: res.append(i) return res
复杂度
时间:O(n * m)O(n∗m),nn为字符串长度,mm为单词个数
空间:O(m)O(m)
javascript
/**
* @param {string} s
* @param {string[]} words
* @return {number[]}
*/
var findSubstring = function(s, words) {
const ret = []
// 统计出现的次数
const map = {}
for (let i = 0; i < words.length; i++) {
const word = words[i]
map[word] = map[word] || 0
map[word]++
}
const sLen = s.length
const wordLen = words[0].length
const wordsNum = words.length
const wordStrLen = wordLen * wordsNum
for (let start = 0; start < sLen - wordStrLen + 1; start++) {
// 当前窗口,当前用于判断的子串
const subStr = s.slice(start, start + wordStrLen)
// 记录个数
const subMap = {}
let subStart = 0
// 处理是否符合,循环中会因不符合时跳出处理,故循环后要判断是否符合
for (; subStart < subStr.length; subStart += wordLen) {
const word = subStr.slice(subStart, subStart + wordLen)
if (!(word in map)) break
subMap[word] = subMap[word] || 0
subMap[word]++
if (subMap[word] > map[word]) break
}
// 上面循环处理了不成立的情况,当subStart在子串的末尾
// 说明当前子串已经遍历完,且当前子串符合
if (subStart === subStr.length) {
ret.push(start)
}
}
return ret
};
/**
* @param {string} s
* @param {string[]} words
* @return {number[]}
*/
var findSubstring = function (s, words) {
const wordLen = words[0].length;
const substrLen = wordLen * words.length;
const initialWordsMap = words.reduce((map, i) => {
map[i] = (map[i] || 0) + 1;
return map;
}, {})
const res = [];
for (let i = 0; i <= s.length - substrLen; i++) {
const wordsMap = { ...initialWordsMap };
for (let j = i; j < i + substrLen; j += wordLen) {
const word = s.slice(j, j + wordLen);
if (!(word in wordsMap) || wordsMap[word] == 0) break;
wordsMap[word]--;
}
const isUseWord = Object.values(wordsMap).every(v => v == 0)
if (isUseWord) res.push(i);
}
return res;
};
将 words 转换为记录单词出现次数的哈希表, words_map = [foo:1 bar:1]
最后将转换后的 s(s1,s2,s3...) 序列按顺序放入到 words 构成的哈希表中去进行对比,例如将 s1 与 words_map 进行对比,可以发现 s1 满足 words_map 单词出现的次数(s1 中包含 [bar foo]),于是获得 s1 对应的单词起始位置 0
Go Code:
func findSubstring(s string, words []string) []int {
if len(s) == 0 || len(words) == 0 {
return []int{}
}
wordsCnt := make(map[string]int, 0)
for _, word := range words {
wordsCnt[word]++
}
wordsLength := len(words)
length := len(words[0])
result := make([]int, 0)
for index := 0; index+wordsLength*length <= len(s); index++ {
ch := s[index : index+length]
if _, ok := wordsCnt[ch]; ok {
sCnt := make(map[string]int, 0)
var j int
for j = 0; j < wordsLength; j ++ {
temp := s[index+j*length : index+(j+1)*length]
sCnt[temp]++
if sCnt[temp] > wordsCnt[temp] {
break
}
}
if j == wordsLength {
result = append(result, index)
}
}
}
return result
}
复杂度分析
令 n 为数组长度。
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<>();
if (s == null || s.length() == 0 || words == null || words.length == 0) return res;
HashMap<String, Integer> map = new HashMap<>();
int one_word = words[0].length();
int word_num = words.length;
int all_len = one_word * word_num;
for (String word : words) {
map.put(word, map.getOrDefault(word, 0) + 1);
}
for (int i = 0; i < s.length() - all_len + 1; i++) {
String tmp = s.substring(i, i + all_len);
HashMap<String, Integer> tmp_map = new HashMap<>();
for (int j = 0; j < all_len; j += one_word) {
String w = tmp.substring(j, j + one_word);
tmp_map.put(w, tmp_map.getOrDefault(w, 0) + 1);
}
if (map.equals(tmp_map)) res.add(i);
}
return res;
}
}
长见识ing。。map也可以equals
其实就是一个定长滑动窗口,一次增加单词的长度
有思路但是。。我太垃圾了
### 代码
// INPUT:
// s = "wordgoodgoodgoodbestword";
// words = { "word", "good", "best", "good" };
// -----------------------------------------------
// OUTPUT:
// 0: word,good,good,good,best,word,
// 1: ordg,oodg,oodg,oodb,estw,
// 2: rdgo,odgo,odgo,odbe,stwo,
// 3: dgoo,dgoo,dgoo,dbes,twor,
// [8]
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
Map<String, Integer> map = new HashMap<>();
for (String word : words) map.put(word, map.getOrDefault(word, 0) + 1);
List<Integer> resList = new ArrayList<>();
int word_num = words.length, one_word = words[0].length();
for (int i = 0; i < one_word; i++) {
Map<String, Integer> checkmap = new HashMap<>();
int start_index = i, pointer = i, count = 0;
while (pointer <= s.length() - one_word) {
String word = s.substring(pointer, pointer + one_word);
pointer += one_word; // for next loop
if (!map.containsKey(word)) {
count = 0;
start_index = pointer;
checkmap.clear();
} else {
checkmap.put(word, checkmap.getOrDefault(word, 0) + 1);
count++;
// backtracking
while (checkmap.get(word) > map.get(word)) {
String start_word = s.substring(start_index, start_index + one_word);
count--;
checkmap.put(start_word, checkmap.get(start_word) - 1);
start_index += one_word;
}
if (count == word_num) resList.add(start_index);
}
}
}
return resList;
}
}
实在没时间,先搬运了别人的代码
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
from collections import Counter
if not s or not words:return []
one_word = len(words[0])
word_num = len(words)
n = len(s)
if n < one_word:return []
words = Counter(words)
res = []
for i in range(0, one_word):
cur_cnt = 0
left = i
right = i
cur_Counter = Counter()
while right + one_word <= n:
w = s[right:right + one_word]
right += one_word
if w not in words:
left = right
cur_Counter.clear()
cur_cnt = 0
else:
cur_Counter[w] += 1
cur_cnt += 1
while cur_Counter[w] > words[w]:
left_w = s[left:left+one_word]
left += one_word
cur_Counter[left_w] -= 1
cur_cnt -= 1
if cur_cnt == word_num :
res.append(left)
return res
题眼:words中的单词长度相同 遍历s中所有长度为 (words[0].length * words.length) 的子串 Y,查看 Y 是否可以由 words 数组构造生成。
如果能完整遍历完子串,说明找到了符合条件的子串,将起始字符的index放入ans
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
unordered_map<string, int> m;
vector<int> ans;
const int length = words[0].length();
const int span = words.size() * length;
const int n = s.length();
for(auto const word : words){
m[word]++;
}
// 注意这里要+1, 因为要到最后一个span的第一个字母
for(int i = 0; i < n - span + 1; ++i){
unordered_map<string, int> temp;
string cur = s.substr(i, span);
int j = 0;
for(; j < span; j += length){
string word = cur.substr(j, length);
if(m.find(word) == m.end())
break;
temp[word]++;
if(temp[word] > m[word])
break;
}
if(j == cur.length())
ans.push_back(i);
}
return ans;
}
};
Complexity Analysis: n=s.size(),m=words.size(), k=words[0].size() T: $O(nmk)$ 因为substr的复杂度是O(N) S: $O(m)$
Copy
https://www.bilibili.com/video/BV1nM4y1V7Wg(参考视频)
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
from collections import Counter
if not s or not words: return []
one_word = len(words[0])
word_num = len(words)
n = len(s)
if n < one_word: return []
# 记录每个单词出现的次数
words = Counter(words)
res = []
# 只需要遍历字符串的一个word长度
for i in range(0, one_word): # O(K)
cur_cnt = 0
# 双指针
left = i
right = i
cur_Counter = Counter()
while right + one_word <= n: # O(N)
# 目前检索的单词
w = s[right:right + one_word]
right += one_word
# 如果目前检索的单词不在words里,挪动左指针
if w not in words:
left = right
cur_Counter.clear()
cur_cnt = 0
else:
cur_Counter[w] += 1
cur_cnt += 1
# 如果目前统计的单词有超出words的单词计数,挪动左指针
while cur_Counter[w] > words[w]: # O(M)
left_w = s[left:left + one_word]
left += one_word
cur_Counter[left_w] -= 1
cur_cnt -= 1
if cur_cnt == word_num:
res.append(left)
return res
令 N 为字符串 S 长度, M 为 words 数组元素个数, K 为单个 word 字串长度
时间复杂度:O(N∗M∗K)
空间复杂度:O(M)
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
allWords = collections.Counter(words)
wordNum = len(words)
wordLen = len(words[0])
res = []
for i in range(len(s) - wordNum * wordLen + 1):
subWords = collections.defaultdict(int)
index = i
while index < i + wordNum * wordLen:
curWord = s[index: index + wordLen]
if curWord not in allWords or subWords[curWord] == allWords[curWord]:
break
subWords[curWord] += 1
index += wordLen
if index == i + wordNum * wordLen:
res.append(i)
return res
Counter + DFS
class Solution:
def findSubstring(self, S: str, W: List[str]) -> List[int]:
N = len(S)
C = Counter(W)
# DFS with Counter
def dfs(i,counter):
# if all words are squentially found return True
if sum(counter.values()) == 0:
return True
for w in counter:
if counter[w] and S[i:i+len(w)] == w:
counter[w] -= 1
if dfs(i + len(w), counter):
return True
counter[w] += 1
return False
# find length all words
len_all_words = sum( len(w) * c for w,c in C.items())
# for each letter in string search if we can make merge words from counter into the next substring
ans = []
for i in range(N-len_all_words+1):
if dfs(i, deepcopy(C)):
ans.append(i)
return ans
N - 字符串长度 NW - 单词数目 NL - 最大子串 LW - 所有单词长度
Time: O( (N-LW) NW NL ) Space: O(NW) * NW
哈希表+滑动窗口
java
public List<Integer> findSubstring(String s, String[] words) {
HashMap<String, Integer> strMap = new HashMap<>();
LinkedList<Integer> ans = new LinkedList<>();
int step=words[0].length();
for (String word:words){
strMap.put(word,strMap.getOrDefault(word,0)+1);
}
for (int i = 0; i <= s.length()-step; i++) {
int start=i;
String sub=s.substring(start,start+step);
if (!strMap.containsKey(sub))continue;
else {
HashMap<String,Integer> tempMap = (HashMap<String, Integer>) strMap.clone();
do {
int left = tempMap.get(sub);
tempMap.put(sub,--left);
if (left==0)tempMap.remove(sub);
start+=step;
if (start+step>s.length())break;
sub=s.substring(start,start+step);
}while (tempMap.containsKey(sub));
if (tempMap.isEmpty())ans.offer(i);
}
}
return ans;
}
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<Integer>();
int n = words.length;
if (n == 0) {
return res;
}
int d = words[0].length();
HashMap<String, Integer> countMap = new HashMap<>();
for (String w : words) {
countMap.put(w, countMap.getOrDefault(w, 0) + 1);
}
for (int j = 0; j < d; j++) {
HashMap<String, Integer> hasWords = new HashMap<>();
int num = 0;
for (int i = j; i < s.length() - n * d + 1; i = i + d) {
boolean hasRemoved = false;
while (num < n) {
String word = s.substring(i + num * d, i + (num + 1) * d);
if (countMap.containsKey(word)) {
int value = hasWords.getOrDefault(word, 0);
hasWords.put(word, value + 1);
if (hasWords.get(word) > countMap.get(word)) {
hasRemoved = true;
int removeNum = 0;
while (hasWords.get(word) > countMap.get(word)) {
String firstWord = s.substring(i + removeNum * d, i + (removeNum + 1) * d);
hasWords.put(firstWord, hasWords.get(firstWord) - 1);
removeNum++;
}
num = num - removeNum + 1;
i = i + (removeNum - 1) * d;
break;
}
} else {
hasWords.clear();
i = i + num * d;
num = 0;
break;
}
num++;
}
if (num == n) {
res.add(i);
}
if (num > 0 && !hasRemoved) {
String firstWord = s.substring(i, i + d);
int v = hasWords.get(firstWord);
hasWords.put(firstWord, v - 1);
num = num - 1;
}
}
}
return res;
}
'''class Solution: def findSubstring(self, s: str, words: List[str]) -> List[int]: counter = Counter(words) M, W, K = len(s), len(words[0]), len(words) @lru_cache(None) def getWords(idx): if idx >= M: return [] w = s[idx: idx+W] return [w] + getWords(idx+W) wsm, ret = {}, [] for i in range(M): wsm[i] = getWords(i) for i in range(M): ws = wsm[i] c = Counter(ws[:K]) if c == counter: ret.append(i) return ret '''
30. 串联所有单词的子串
入选理由
暂无
题目地址
https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words
前置知识
题目描述
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
示例 1: 输入: s = "barfoothefoobarman", words = ["foo","bar"] 输出:[0,9] 解释: 从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。 输出的顺序不重要, [9,0] 也是有效答案。 示例 2:
输入: s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"] 输出:[]