「前端刷题」30. 串联所有单词的子串

723 阅读2分钟

这是我参与8月更文挑战的第30天,活动详情查看:8月更文挑战

题目

leetcode-cn.com/problems/su…

给定一个字符串 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 <= 104
  • s 由小写英文字母组成
  • 1 <= words.length <= 5000
  • 1 <= words[i].length <= 30
  • words[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