leetcode 17.18最短超串(滑动窗口)

168 阅读3分钟

最短超串

假设你有两个数组,一个长一个短,短的元素均不相同。找到长数组中包含短数组所有的元素的最短子数组,其出现顺序无关紧要。

返回最短子数组的左端点和右端点,如有多个满足条件的子数组,返回左端点最小的一个。若不存在,返回空数组。

示例 1:

输入: big = [7,5,9,0,2,1,3,5,7,9,1,1,5,8,8,9,7] small = [1,5,9] 输出: [7,10] 示例 2:

输入: big = [1,2,3] small = [4] 输出: [] 提示:

big.length <= 100000 1 <= small.length <= 100000

解题

1.暴力双循环

思路:内循环遍历big数组,small中的第一个元素第一次出现记为start,直到small数组的所有元素第一次出现记为end,此时start,end为一组超串。外循环遍历所有的超串,寻找最短超串

代码实现:

var shortestSeq = function (big, small) {
  let res = []
  let obj = {}
  let c = small.length
  let dist = big.length
  small.forEach(item => {
    obj[item] = -1
  })
  for (let i = 0; i < big.length; i++) {
    let tmp_c = c
    let start = (end = -1)
    if (!obj[big[i]]) {
      continue
    }
    for (let j = i; j < big.length; j++) {
      let cur = big[j]
      if (obj[cur] && obj[cur] != i + 1) {
        obj[cur] = i + 1
        tmp_c -= 1
        if (start === -1) {
          start = j
        }
      }
      if (tmp_c === 0) {
        end = j
        if (dist > end - start) {
          res = [start, end]
          dist = end - start
        }
        break
      }
    }
  }
  return res
}

2.求数组交集

思路:

  • 第一步构建一个缓存对象obj,key为small的每个元素,value为该元素在big数组中的索引位置数组A1。遍历big数据,将small数组的每个元素出现的索引放入value数组中
  • 第二步,依次从obj的每个value中取第一项,形成新的有序数组A2,数组的最大值和最小值即为一个超串
  • 第三步,取出数组A2的第一项,并将该项所在value数组A1的下一项放入数组A2,并保证A2有序,此时的A2数组的最大值和最小值即为一个新的超串
  • 重复第三步,直到任何一个A1数组为空

代码实现:

var shortestSeq = function (big, small) {
  let res = []
  let dist = big.length
  let obj = {}
  let c = (c1 = small.length)
  let arr = []
  const search = function (element, arr) {
    let left = 0
    let right = arr.length - 1

    while (left <= right) {
      const mid = Math.floor((left + right) / 2)

      if (arr[mid] === element) {
        return mid
      } else if (arr[mid] < element) {
        left = mid + 1
      } else {
        right = mid - 1
      }
    }

    return left
  }
  small.forEach(item => {
    obj[item] = []
  })
  big.forEach((item, idx) => {
    if (item in obj) {
      let tm_arr = obj[item]
      tm_arr.push(idx)
      if (tm_arr.length === 1) {
        c -= 1
      }
    }
  })
  //表明每一个small都在big中
  if (c === 0) {
    //表明big===small
    if (big.length === small.length) {
      return [0, big.length - 1]
    }
    Object.keys(obj).forEach(item => {
      arr.push(obj[item].shift())
    })
    arr.sort((a, b) => a - b)
    while (true) {
      if (dist > arr[c1 - 1] - arr[0]) {
        res = [arr[0], arr[c1 - 1]]
        dist = arr[c1 - 1] - arr[0]
      }
      if (obj[big[arr[0]]].length === 0) {
        break
      }
      let tmp_arr = obj[big[arr.shift(0)]]
      let tmp = tmp_arr.shift()
      //数组已经有序了,二分查找要插入的位置
      let idx = search(tmp, arr)
      // arr.push(tmp)
      arr.splice(idx, 0, tmp)
    }
  }
  return res
}

3.滑动窗口

思路:遍历big数组

  • 第一步:扩张寻找最右边界,small中的所有元素至少出现一次,用diff=small.length表示。判断逻辑:基于small初始一个对象obj,key为small[i],value为1,当遇到key时,value-1,判断value>=0,表示此key第一次出现diff-=1,当diff=0时,表示small中所有元素至少出现一次
  • 第二步:压缩左边界,直到small中的任意一个元素只剩余一个。判断逻辑:当遇到key时,value+1,当value>0时,表示key只剩余一个,此时左右边界为最小边界。继续压缩左边界,同时diff+=1,表示还需要再寻找一个key(压缩前key是最后一个)
  • 第三步:重复第一二步,直到右边界到big的结尾

代码实现:

var shortestSeq = function (big, small) {
  let len = big.length
  let res = []
  let obj = {}
  let diff = small.length
  small.forEach(item => {
    obj[item] = 1
  })
  const checkVal = item => item in obj
  for (let l = 0, r = 0; r < big.length; r++) {
    if (checkVal(big[r]) && --obj[big[r]] >= 0) {
      diff -= 1
    }
    while (diff === 0) {
      if (r - l < len) {
        len = r - l
        res = [l, r]
      }
      if (checkVal(big[l]) && ++obj[big[l]] > 0) {
        diff += 1
      }
      l += 1
    }
  }
  return res
}