题目描述
描述:假设你有两个数组,一个长一个短,短的元素均不相同。找到长数组中包含短数组所有的元素的最短子数组,其出现顺序无关紧要。
返回最短子数组的左端点和右端点,如有多个满足条件的子数组,返回左端点最小的一个。若不存在,返回空数组。
示例 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
解题思路
思路:最直观的想法是,滑动窗口。使用need表示窗口内需要包含的元素以及对应个数,此处只记录small中出现的元素及个数,diff表示所需要覆盖的所有数字总和,minLen表示滑动窗口的最短长度。首先遍历一遍small填充need和diff,然后开始滑动窗口,l表示左边界,r表示右边界,当不满足条件时移动右边界而当满足条件时移动左边界,其中判断右边界时,如果当前元素在need中可以找到,则分为两种情况,第一种是need[i]大于0,即还需求该元素,那么need[i]和diff均减一,第二种是need[i]小于等于0,即不需求该元素甚至该元素还有多余,那么need[i]需要减一,其负数表示多余多少个,而diff不需要减一,因为此时是该元素多余,但是所需求的其他元素不变的,当diff等于0即表示满足要求,那么就可以开始移动左边界并更新最小滑动窗口,判断左边界时同理根据语义分析。注意,diff和need虽然是同步变化,但是两者又有些许不同,diff是大于等于0的,而need是可以小于0的,其负数表示该数多余的个数,故在变化时需要注意一些细节。
class Solution {
public:
vector<int> shortestSeq(vector<int>& big, vector<int>& small) {
int n=big.size();
vector<int> res;
//need表示窗口内需要包含的元素以及对应的个数
unordered_map<int,int> need;
//diff表示需要覆盖的所有数字总数
int diff=0;
//minLen表示滑动窗口的最短长度
int minLen=n;
//通过small统计need
for(auto& e:small)
{
need[e]++;
diff++;
}
//滑动窗口 l指向左边界 r指向右边界
int l=0,r=0;
//当不满足条件时移动右边界而当满足条件时移动左边界
for(;r<n;r++)
{
//当前需要则需要个数以及覆盖个数均减一
if(need.find(big[r])!=need.end())
{
//之前有需求则减去总需要 反之没有则不需要减去
if(need[big[r]]>0)
diff--;
//虽然diff和need[big[r]]是同步变化的 但是只有有需求才会减去diff 但无论如何都会减去need need为负数表示多余
need[big[r]]--;
}
//如果diff为0则向左收缩窗口
while(diff==0)
{
//更新最小窗口
if(r-l<minLen)
{
minLen=r-l;
res={l,r};
}
if(need.find(big[l])!=need.end())
{
if(need[big[l]]>=0)
diff++;
need[big[l]]++;
}
l++;
}
}
return res;
}
};
总结:滑动窗口一般用于解决查找满足一定条件的连续区间的性质,比如在字符串或者数组中查找目标字符串或者数组所出现的最小区间。滑动窗口一般维护两个指针,即左指针和右指针,当窗口内元素尚未满足条件时,右指针右移,以探索未知区间,当窗口内元素已经满足条件时,左指针右移,以获得更小区间。