【笔记】JavaScript版数据结构与算法——基础算法之“递归类”(30. 串联所有单词的子串【排列组合算法】)

30. 串联所有单词的子串

1.题目

30. 串联所有单词的子串 - 力扣(LeetCode)

给定一个字符串 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"]
  • 输出:
[]

题目模板

/**
 * @param {string} s
 * @param {string[]} words
 * @return {number[]}
 */
var findSubstring = function(s, words) {

};

2.思路分析

  • words排列组合
  • 寻找各个子串在s中的初始位置并push进arr作为结果返回

本题重点是实现一个排列组合算法,实现原理如图:
在这里插入图片描述
细节详见题解

3.所用到的方法

见题解

4.题解及优化

我的题解

let findSubstring = (s, words) => {
  let res = []
  let rewords = []
  let len = words.length
  /**
   * 【排列组合算法】
   * 说明:func用来对words中的元素进行排列组合,将排列好的各个结果存在新数组中,并返回
   * @param arr:排列好的元素
   * @param left_words:待排列元素
   */
  let func = (arr, left_words) => {
    if (arr.length === len) {
      rewords.push(arr)
    } else {
      left_words.forEach((item, index) => {
        let temp = [].concat(left_words)
        temp.splice(index, 1)
        // 此时,第一个参数是当前分离出的元素所在数组;第二个参数temp是传入的new_words去掉第一个后的结果
        func(arr.concat(item), temp)
      })
    }
  }
  func([], words)
  rewords.map(
    item => {
      for (let i = 0; i < s.length; i++) {
        i = s.indexOf(item.join(''), i) // 找到每个子串在s中的位置
        if (i === -1 || item.length === 0) {
          break
        } else {
          res.push(i)
        }
      }
    }
  )
  return [...new Set(res.filter(value => value >= 0).sort())] // 筛选剔除-1项
}
console.log('结果', findSubstring('a', [])) // []
// console.log('结果', findSubstring('foobarfoobar', ['foo', 'bar'])) // 0,3,6
// console.log('结果', findSubstring('wordgoodgoodgoodbestword', ['word', 'good', 'best', 'good'])) // 8
// console.log('结果', findSubstring('CADDBCACDBDACCAADBCA', ['A', 'B', 'C', 'D'])) // 3,6,9,15,16

注意,本题有两个容易忽视的点:

  • 去重:words中含有相同word时,将会出现两个相同的子串,也就会导致结果中有重复
    • 测试用例:findSubstring('wordgoodgoodgoodbestword', ['word', 'good', 'best', 'good']) // 8
    • 解决方法:去重,子串数组去重和结果去重都可以,如果中间结果中待加工“子串”是二维数组的话去重会麻烦些,可以直接在结果中去重,将return res改为 return [...new Set(res)],子串数组去重可以采用在push前使用includes判断。
  • 多个符合匹配的位置
    • 测试用例:findSubstring(‘foobarfoobar’, [‘foo’, ‘bar’]) // 0,3,6
    • 解决方法:将:res = rewords.map(i => s.indexOf(i.join(''))).filter(v => v >= 0).sort()修改为如下:
rewords.map(
  item => {
    for (let i = 0; i < s.length; i++) {
      i = s.indexOf(item.join(''), i) // 找到每个子串在s中的位置
      if (i === -1) {
        break
      } else {
        res.push(i)
      }
    }
  }
)
res = res.filter(value => value >= 0).sort()

当数据量大的时候组合数太多导致内存溢出。。。最终我的题解被下面这个测试用例弄崩溃了。。。:

"pjzkrkevzztxductzzxmxsvwjkxpvukmfjywwetvfnujhweiybwvvsrfequzkhossmootkmyxgjgfordrpapjuunmqnxxdrqrfgkrsjqbszgiqlcfnrpjlcwdrvbumtotzylshdvccdmsqoadfrpsvnwpizlwszrtyclhgilklydbmfhuywotjmktnwrfvizvnmfvvqfiokkdprznnnjycttprkxpuykhmpchiksyucbmtabiqkisgbhxngmhezrrqvayfsxauampdpxtafniiwfvdufhtwajrbkxtjzqjnfocdhekumttuqwovfjrgulhekcpjszyynadxhnttgmnxkduqmmyhzfnjhducesctufqbumxbamalqudeibljgbspeotkgvddcwgxidaiqcvgwykhbysjzlzfbupkqunuqtraxrlptivshhbihtsigtpipguhbhctcvubnhqipncyxfjebdnjyetnlnvmuxhzsdahkrscewabejifmxombiamxvauuitoltyymsarqcuuoezcbqpdaprxmsrickwpgwpsoplhugbikbkotzrtqkscekkgwjycfnvwfgdzogjzjvpcvixnsqsxacfwndzvrwrycwxrcismdhqapoojegggkocyrdtkzmiekhxoppctytvphjynrhtcvxcobxbcjjivtfjiwmduhzjokkbctweqtigwfhzorjlkpuuliaipbtfldinyetoybvugevwvhhhweejogrghllsouipabfafcxnhukcbtmxzshoyyufjhzadhrelweszbfgwpkzlwxkogyogutscvuhcllphshivnoteztpxsaoaacgxyaztuixhunrowzljqfqrahosheukhahhbiaxqzfmmwcjxountkevsvpbzjnilwpoermxrtlfroqoclexxisrdhvfsindffslyekrzwzqkpeocilatftymodgztjgybtyheqgcpwogdcjlnlesefgvimwbxcbzvaibspdjnrpqtyeilkcspknyylbwndvkffmzuriilxagyerjptbgeqgebiaqnvdubrtxibhvakcyotkfonmseszhczapxdlauexehhaireihxsplgdgmxfvaevrbadbwjbdrkfbbjjkgcztkcbwagtcnrtqryuqixtzhaakjlurnumzyovawrcjiwabuwretmdamfkxrgqgcdgbrdbnugzecbgyxxdqmisaqcyjkqrntxqmdrczxbebemcblftxplafnyoxqimkhcykwamvdsxjezkpgdpvopddptdfbprjustquhlazkjfluxrzopqdstulybnqvyknrchbphcarknnhhovweaqawdyxsqsqahkepluypwrzjegqtdoxfgzdkydeoxvrfhxusrujnmjzqrrlxglcmkiykldbiasnhrjbjekystzilrwkzhontwmehrfsrzfaqrbbxncphbzuuxeteshyrveamjsfiaharkcqxefghgceeixkdgkuboupxnwhnfigpkwnqdvzlydpidcljmflbccarbiegsmweklwngvygbqpescpeichmfidgsjmkvkofvkuehsmkkbocgejoiqcnafvuokelwuqsgkyoekaroptuvekfvmtxtqshcwsztkrzwrpabqrrhnlerxjojemcxel"
["dhvf","sind","ffsl","yekr","zwzq","kpeo","cila","tfty","modg","ztjg","ybty","heqg","cpwo","gdcj","lnle","sefg","vimw","bxcb"]

课程解法

let findSubstring = (s, words) => {
  // 计算字符串的总长度
  let strLen = s.length
  // 计算所有的单词数量
  let wordsLen = words.length
  // 计算所有单词出现的起始位置和截止位置
  let pos = {}
  // 如果字符串的长度小于所有单词的总长度直接返回
  if (strLen < words.join('').length) {
    return []
  }
  // 遍历所有单词查找在字符串中的起始位置和截止位置
  words.every(word => {
    if (pos[word]) {
      return true
    }
    let wl = word.length
    let tmp = []
    for (let i = 0, len = strLen - wl, idx; i <= len; i++) {
      idx = s.slice(i).indexOf(word)
      if (idx > -1) {
        if (idx === 0) {
          tmp.push({
            start: i,
            end: i + wl
          })
        } else if (s[i + 1] !== word[0]) {
          i += idx - 1
        }
      } else {
        break
      }
    }
    // 如果没有匹配到单词终止遍历
    if (tmp[0] === undefined) {
      return false
    } else {
      // 保存当前单词的位置,遍历下一个单词
      pos[word] = tmp.sort((a, b) => a.start - b.start)
      return true
    }
  })
  // 只要有一个单词没找到说明不能匹配到连续的字符串
  if (words.find(item => !pos[item])) {
    return []
  }
  let result = []
  // 计算所有单词的位置
  let match = (poses) => {
    // 记录是不是所有单词都被匹配到了,每一次都应该把所有单词都包括进来并且是相邻的
    let record = []
    let len = Object.keys(poses).length
    // 如果没有单词的位置说明处理结束了
    if (len < 1) {
      return -1
    }
    while (1) {
      // 每次循环应该把记录清空
      record.length = 0
      // 按照起始位置进行升序排序
      let minV = Number.MAX_SAFE_INTEGER
      let minK = ''
      // 优先找到所有单词其实位置最小的单词开始匹配
      for (let [k, v] of Object.entries(poses)) {
        if (!v.length) {
          return false
        } else {
          if (v[0].start < minV) {
            minK = k
            minV = v[0].start
          }
        }
      }
      if (!minK) {
        return false
      }
      // 起始位置最小的单词
      let first = poses[minK].shift()
      if (!first) {
        return false
      }
      // 记录下这个起始位置
      let start = first.start
      // 记录words列表中的单词
      record.push(words.findIndex(item => item === minK))
      // 每次循环要匹配到所有单词
      for (let i = 1; i < wordsLen; i++) {
        for (let j = 0, next; j < wordsLen; j++) {
          if (record.includes(j)) {
            continue
          }
          if (poses[words[j]][0] === undefined) {
            return false
          }
          next = poses[words[j]].find(item => item.start === first.end)
          if (next) {
            record.push(j)
            first = next
            break
          }
        }
      }
      // 如果所有单词的顺序是挨着的,记录下当前的起始位置
      if (record.length === wordsLen && !record.find(item => item === undefined)) {
        result.push(start)
      }
    }
  }
  match(pos)
  // 对 result 去重,如 result=[1,1,2,3] [...new Set(result)]===[1,2,3]
  return [...new Set(result)]
}

算法是利用查找每个单词在字符串的位置,然后通过计算这些位置是不是连续的。比如 abfoobarcd,[foo,bar],那么for的起始位置是2,bar的起始位置是5;说明这两个单词是连续的2+3(for的长度)=5
foo:[{start:2,end:5}]
bar:[{start:5,end:8}]
判断上一个单词的end和下一个单词的start是不是相同来计算两个单词是不是挨着

其他小伙伴的解法

解法一:暴力求解
let findSubstring = (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;
  }

思路详见原作者博客

解法二:滑动窗口
/**
 * @param {string} s
 * @param {string[]} words
 * @return {number[]}
 */
var findSubstring = function(s, words) {
    if (!s || !words || !words.length) return [];
    let windows = {}, needs = {}, oneWordLen = words[0].length;
    for (let w of words) {
        needs[w] ? needs[w]++ : needs[w] = 1;
    }
    let l = 0, r = 0, count = 0, needsKeyLen = Object.keys(needs).length, ans = [];
    for (let i = 0; i < oneWordLen; i++) {
        windows = {};
        r = l = i;
        count = 0;
        while (r <= s.length - oneWordLen) {
            let w1 = s.slice(r, r + oneWordLen);
            r += oneWordLen;
            if (!needs[w1]) {
                windows = {};
                l = r;
                count = 0;
                continue;
            }
            windows[w1] ? windows[w1]++ : windows[w1] = 1;
            if (windows[w1] === needs[w1]) count++;
            while (count === needsKeyLen) {
                if (r - l === oneWordLen * words.length) ans.push(l);
                let w2 = s.slice(l, l + oneWordLen);
                l += oneWordLen;
                if (needs[w2]) {
                    windows[w2]--;
                    if (windows[w2] < needs[w2]) count--;
                }
            }
        }
    }
    return ans;
};

思路详见原作者博客


更加过分的测试用例:

"abababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab"
["ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba","ab","ba"]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序边界

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值