1. 需要输出或比较的结果在原数据结构中是连续排列的;
2. 每次窗口滑动时,只需观察窗口两端元素的变化,无论窗口多长,每次只操作两个头尾元素,当用到的窗口比较长时,可以显著减少操作次数;
3. 窗口内元素的整体性比较强,窗口滑动可以只通过操作头尾两个位置的变化实现,但对比结果时往往要用到窗口中所有元素。
尺取法就是双指针(two points)嘛。而滑动窗口也要用到二个指针,所以滑动窗口是一种特殊的尺取法。
leetcode 239. 滑动窗口最大值
题意:
在一个数组中找长度为k的子序列 中的最大值,如下图所示:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
题解:
长度为k的子序列,说明窗口的大小是固定的,且为k。
找每个窗口中的最大值,我们可以遍历窗口中的所有值,从而找到最大值,此时,时间复杂度为O(n * m)
用单调双端队列找最大值可以使时间复杂度变为O(n)。
下面用的单调双端队列 是单调递减的。那么队列的第一个元素即为最大值,而且,队列前面的数 一定 比队列后面的数放入时间早。
class Solution {
public:
vector maxSlidingWindow(vector& nums, int k) {
vector res;
if (nums.size() <= 0 || k < 1 || k > nums.size()){
return res;
}
deque list;//创建双端队列
for (int i = 0; i < nums.size(); i++){ //i表示窗口的结尾下标
while (list.size() != 0 && nums[*(list.end() - 1)] <= nums[i]){
list.pop_back();
}//while
list.push_back(i);
if (*list.begin() == i - k){
list.pop_front();
}//if
if (i >= k - 1){ //i 要到 k - 1位置处才是一个完整的窗口
res.push_back(nums[*list.begin()]);
}//if
}//for
return res;
}
};
最大值减去最小值小于等于num 的子数组数量
要求: 如果数组长度为N,请实现时间复杂度为O(N)的解法。
暴力解法:
class Solution {
public:
int getNum(vector arr, int num){
int res = 0;
for (int start = 0; start < arr.size(); start++){
for (int end = start; end < arr.size(); end++){
if (isValid(arr, start, end, num)){
//cout << start << " " << end << endl;
res++;
}//if
}//inner for
}//extren for
return res;
}
bool isValid(vector arr, int start, int end, int num){
int Max = INT_MIN;
int Min = INT_MAX;
for (int i = start; i <= end; i++){
Max = max(Max, arr[i]);
Min = min(Min, arr[i]);
}
return Max - Min <= num;
}
};
int main()
{
vector arr;
arr.push_back(1);
arr.push_back(2);
arr.push_back(3);
Solution s;
cout << s.getNum(arr, 1) << endl;
return 0;
}
用窗口,先移动窗口的R,当窗口内的最大值减最小值 大于num , 说明要窗口要变小,此时我们移动L,直到窗口内的最大值减最小值 小于等于num。 这个过程循环进行,直到L 和 R 都到达数组尾部。
每移动一次L , 说明以L - 1 开头的子数组,已经找到所有满足题意的可能性了。
class Solution {
public:
int getNum(vector arr, int num){
if (arr.size() == 0) return 0;
int res = 0;
//创建两个记录最大值和最小值的双端队列
deque dpMin;
deque dpMax;
//设置左右指针
int L = 0;
int R = 0;
while (L < arr.size()){ // move left point
while (R < arr.size()){ //move right point
//更新两个双端队列
while (dpMax.size() != 0 && arr[*(dpMax.end() - 1)] <= arr[R]){
dpMax.pop_back();
}
dpMax.push_back(R);
while (dpMin.size() != 0 && arr[*(dpMin.end() - 1)] >= arr[R]){
dpMin.pop_back();
}//while
dpMin.push_back(R);
if (arr[*dpMax.begin()] - arr[*dpMin.begin()] > num){
break;
}
R++;
}//medium while
//窗口的右边界要移动了
if (*dpMax.begin() == L){
dpMax.pop_front();
}
if (*dpMin.begin() == L){
dpMin.pop_front();
}
res += R - L;
L++;
}//extren while
return res;
}
};
int main()
{
vector arr;
arr.push_back(1);
arr.push_back(2);
arr.push_back(3);
Solution s;
cout << s.getNum(arr, 1) << endl;
return 0;
}
用了三层的while,可是时间复杂度仅仅为O(n)。因为窗口的L 和 R 都不回退的。
可以看到窗口 和 单调双端队列 配合使用,可以在O(1) 的时间内找到窗口内的最值。
题目:
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
题解:
窗口,移动右指针,当满足要求,就移动左指针来达到减小窗口内 sum 的效果。当左指针移动到sum 小于 s 了,就又移右指针,相当于扩大窗口,从而达到增加sum 的效果。
class Solution {
public:
int minSubArrayLen(int s, vector& nums) {
int len = nums.size();
if (len == 0){
return 0;
}
int ans = INT_MAX;
int sum = 0, L = 0, R = 0;
while (R < len){
sum += nums[R];
R++;
while (sum >= s){
ans = min(ans, R - L);
sum -= nums[L];
L++;
}//inner while
}//extren while
return ans == INT_MAX ? 0 : ans;
}
};
题意:
对一个给定的自然数M,求出所有的连续的自然数段,这些连续的自然数段中的全部数之和为M。
例子:1998+1999+2000+2001+2002 = 100001998+1999+2000+2001+2002=10000,所以从1998到2002的一个自然数段为M=10000的一个解。
题解:
用i,j代表区间的左右端点
当sum小于目标值M时,将右端点右移(j++),sum会变大
当sum大于目标值M时,将左端点右移(i++),sum会变小
在双指针移动的过程中,如果有sum==M的情况就输出。
因为两个指针都是单调向右移动,也只扫一遍,可以证明时间复杂度为O(n) 左端点大于m/2时即可停止,因为只要长度为2的连续序列和就一定大于m。
#pragma GCC optimize(3,"Ofast","inline")
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
解二:先枚举开头元素i,其结尾元素用二分查找。用高斯公式求和!
题目:
输入n ( n<= 100,000)个整数,找出其中的两个数,它们之和等于整数m(假定
肯定有解)。题中所有整数都能用 int 表示
题解:
1) 将数组排序,复杂度是O(n×log(n))
2) 查找的时候,设置两个变量i和j,i初值是0,j初值是n-1.看a[i]+a[j],如果大于m,就让j 减1,如果小于m,就让i加1,直至a[i]+a[j]=m。
这种解法总的复杂度是O(n×log(n))的。(尺取法)
#include
#include
using namespace std;
int main()
{
int n, m, i, j;
static int a[100002];
scanf("%d", &n);
scanf("%d", &m);
for (i = 0; i < n; i++) scanf("%d", &a[i]);
sort(a, a + n);
i = 0, j = n - 1;
while (a[i] + a[j] != m){
if (a[i] + a[j]>m) j--;
else if (a[i] + a[j] < m) i++;
}
printf("%d %d\n",a[i],a[j]);
return 0;
}