这是我参与8月更文挑战的第30天,活动详情查看:8月更文挑战
题目
给定一个字符串 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"]
输出: []
示例 3:
输入: s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
输出: [6,9,12]
提示:
1 <= s.length <= 104s由小写英文字母组成1 <= words.length <= 50001 <= words[i].length <= 30words[i]由小写英文字母组成
解题思路
思路1,滑动窗口
/**
* @param {string} s
* @param {string[]} words
* @return {number[]}
*/
var findSubstring = function(s, words) {
let left = 0,right = 0,wordsLen = words.length;
if(wordsLen == 0) return [];
let res = [];
let gapLen = words[0].length;
let needs = {};
let windows = {};
for(let i = 0;i < wordsLen;i++){
needs[words[i]] ? needs[words[i]]++ : needs[words[i]] = 1;
}
let needsLen = Object.keys(needs).length;
let match = 0;
for(let i = 0;i < gapLen;i++){
right = left = i;
match = 0;
while(right <= s.length - gapLen){
let c1 = s.substring(right,right + gapLen);
right += gapLen;
windows[c1] ? windows[c1]++ : windows[c1] = 1;
if(windows[c1] === needs[c1]){
++match;
}
while(left < right && match == needsLen){
if(Math.floor((right - left) / gapLen) == wordsLen){
res.push(left);
}
let c2 = s.substring(left,left + gapLen);
left += gapLen;
windows[c2]-- ;
if(needs[c2] && windows[c2] < needs[c2]){
match--;
}
}
}
windows = {};
}
return res;
};
思路2
输入:
s = "barfoothefoobarman",
words = ["foo","bar"]
第一步:遍历 words 的每一个元素,以 word 为起点切割 s,得到切割后的片段数组 allValues = ['foothe'、'foobar'、'barfoo'、'barman']。
第二步:遍历 allValues 的每一个元素,对每个元素进行切割(如:'foobar' 切割成 ['foo', 'bar']),接着对元素数组进行排序(如:['foo', 'bar'] 排序成 ['bar', 'foo']),然后用 join 方法合成字符串,最后得到 valuesHadSorts = ['foothe'、'barfoo'、'barfoo'、'barman']。
第三步:对 words 进行排序,然后把排序后的 words 用 join 方法组合成字符串 w。
第四步:遍历 valuesHadSorts 的每一个元素,判断是否等于 w,相等则获取它们的 index。
var findSubstring = function(s, words) {
const findIndexs = (str, p) => {
var positions = []
var pos = str.indexOf(p)
while (pos > -1) {
positions.push(pos)
pos = str.indexOf(p, pos + 1)
}
return positions
}
const getValues = (s, word, sliceLength) => {
let arr = []
let indexs = findIndexs(s, word)
indexs.forEach((index) => {
if (index !== -1) {
let leftStrEnd = index + word.length
let leftStrStart = leftStrEnd - sliceLength
if (leftStrStart >= 0 && leftStrEnd <= s.length) {
let leftStr = s.substring(leftStrStart, leftStrEnd)
arr.push(leftStr)
}
}
})
return arr
}
const getAllValues = (s, words) => {
let values = []
let wordSets = [...new Set(words)]
for (let i = 0; i < wordSets.length; i++) {
values = values.concat(getValues(s, wordSets[i], words.length * words[0].length))
}
return [...new Set(values)]
}
const isMatch = (value, words) => {
let arr = []
let v = value.slice()
let len = words[0].length
while (v) {
arr.push(v.substring(0, len))
v = v.substring(len)
}
return arr.sort().join('') === words.sort().join('')
}
if (!s || words.length === 0 || s.length < words.length * words[0].length) return []
let arr = []
const values = getAllValues(s, words)
values.forEach((item) => {
if (isMatch(item, words)) {
arr = arr.concat(findIndexs(s, item))
}
})
return arr
};
思路3
其实刚开始可以做发散,有关子串不规定长度,结果如其他解法一样,当遇到 s="ababaab",words=["ab","ba","ba"]的情况时就会出现重复单词查找,导致结果不匹配,“babaab”这个串被解析为“b,ab,a,ab”,这又涉及到words组合排序的问题,所以为了简化难度,将words的单位长度固定,“babaab”被解析为“ba,ba,ab”,由此可得基础算法;
function findSubstring(s,words){
let len=0;
let le=0;
for(let i in list){
len+=list[i].length;
le=list[i].length;
}
let rst=[];
let stMap={};
for(let i in list){
if(!stMap[list[i]]){
stMap[list[i]]=1;
}else {
stMap[list[i]]+=1;
}
}
for(let i=0;i<=str.length-len;i++){
let st=str.substr(i,len);
let exist=true;
let smap={};
for(let i=0;i<len;i+=le){
let s=st.substr(i,le);
if(!smap[s]){
smap[s]=0;
}
smap[s]+=1;
}
let e=0;
for(let k in smap){
if(stMap[k]&&smap[k]==stMap[k]){
e+=1;
}else {
exist=false;
break;
}
}
if(!exist||e==0){
continue;
}
rst.push(i);
}
return rst;
}
上面的代码由于js对象创建较多,导致在大量数据运算时,消耗内存严重,由此可简化对象创建的次数,用属性初始化指针代替:
var findSubstring = function(s, words) {
let len;
if(words.length>0){
len=words[0].length;
}
return existsStr(s,words,len);
};
function initListMap(list){
let map={};
for(let i of list){
if(!map[i]){
map[i]={start:0};
}
map[i].start+=1;
}
return map;
}
function formateListMap(map){
for(let i in map){
map[i]["exist"]=map[i].start;
}
}
function existsStr(str,list,len){
let rst=[];
if(len){
let long=len*list.length;
let listMap=initListMap(list);
for(let i=0;i+long<=str.length;i++){
let st=str.substr(i,long);
formateListMap(listMap);
let exist=true;
for(let k=0;k<long;k+=len){
let s=st.substr(k,len);
if(listMap[s]&&listMap[s].exist>0){
listMap[s].exist=listMap[s].exist-1;
}else {
exist=false;
break;
}
}
if(!exist){
continue;
}
rst.push(i);
}
}
return rst;
}
变化在于map在重复调用的时候重置属性,并没有重复创建大量的相似对象,极大降低对内存的消耗,其实操作由于其重复性可简化为递归算法,但是大数据可能导致栈溢出,在此不做推荐
最后
曾梦想仗剑走天涯
看一看世界的繁华
年少的心总有些轻狂
终究不过是个普通人
无怨无悔我走我路
「前端刷题」No.30