最短超串
假设你有两个数组,一个长一个短,短的元素均不相同。找到长数组中包含短数组所有的元素的最短子数组,其出现顺序无关紧要。
返回最短子数组的左端点和右端点,如有多个满足条件的子数组,返回左端点最小的一个。若不存在,返回空数组。
示例 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
}