左程云 单调队列

经典用法:滑动窗口的最大值

队列从L口到R口依次递减,每次在右端加一个数,如果数比队尾的数大,就可以把队尾的数全部都弹出来,因为这个数又新又大,然后队头的数就是滑动窗口的最大的数

239. 滑动窗口最大值 - 力扣(LeetCode)

class Solution {
public:
    static const int maxn=100001;
    int deque[maxn];
    int h,t;
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
    int n=nums.size();
    //先形成长度为k-1的窗口,之后每次要不加数,要不减数,相等时候也挤出来
    vector<int> ans;
    for(int i=0;i<k-1;i++)
    {
        while(h<t&&nums[deque[t-1]]<=nums[i])
        t--;
        deque[t++]=i;
    }
    for(int i=k-1;i<n;i++)
    {
        //先加进来一个数,凑够k长度
        while(h<t&&nums[deque[t-1]]<=nums[i])
        t--;
        deque[t++]=i;
        //记录答案
        ans.push_back(nums[deque[h]]);
        //看看队列里面有没有要淘汰的,淘汰掉,为下一次做准备
        if(deque[h]==(i-(k-1)))
        h++;
    }
    return ans;  
    }
};

绝对差不超过限制的最长连续子数组

1438. 绝对差不超过限制的最长连续子数组 - 力扣(LeetCode)

这是一个变长的滑动窗口的最大最小值的差的问题,每次往右边加数时候,窗口最大值一定增长或者不变,最小值一定减小或者不变

针对于每一个l,往右边一直增加数,一直往后记答案

class Solution {
public:
    static const int maxn=100001;
    int maxdeque[maxn];
    int mindeque[maxn];
    int maxh,maxt,minh,mint;
    vector<int> nums1;
    int longestSubarray(vector<int>& nums, int limit) {
    int n=nums.size();
    nums1=nums;
    int ans=0;
    for(int l=0,r=0;l<n;l++)
    {
        while(r<n&&ok(limit,nums[r]))
        {
           //满足条件就往后加数
           while(maxh<maxt&&nums[maxdeque[maxt-1]]<=nums[r])
           maxt--;
           maxdeque[maxt++]=r;
           while(minh<mint&&nums[mindeque[mint-1]]>=nums[r])
           mint--;
           mindeque[mint++]=r++;
        } 
        ans=max(ans,r-l);
        //吐出l位置的数,检查过期
        if(maxh<maxt&&maxdeque[maxh]==l)
        maxh++;
        if(minh<mint&&mindeque[minh]==l)
        minh++;
    }
    return ans;
    }
    //ok函数判断加入这个数后是否还满足限制条件
    bool ok(int limit,int number)
    {
        int maxval=(maxh<maxt)?max(nums1[maxdeque[maxh]],number):number;
        int minval=(minh<mint)?min(nums1[mindeque[minh]],number):number;
        return maxval-minval<=limit;
    }
};

P2698 [USACO12MAR] Flowerpot S - 洛谷

和上道题道理一样就是要满足窗口内花盆的最高减去最低大于等于时间D

#include<bits/stdc++.h>
using namespace std;
const int maxn=100005;
pair<int,int> arr[maxn];//坐标->高度 
int n,limit;
int maxdeque[maxn],mindeque[maxn],maxh,maxt,minh,mint;
bool ok()
{
   int maxval=maxh<maxt?arr[maxdeque[maxh]].second:0;
   int minval=minh<mint?arr[mindeque[minh]].second:0;
   return maxval-minval>=limit;
}
int main()
{
	cin>>n>>limit;
	for(int i=0;i<n;i++)
	cin>>arr[i].first>>arr[i].second;
	sort(arr,arr+n);
	int ans=INT_MAX; 
	for(int l=0,r=0;l<n;l++)
	{
		//不满足大于就一直向后移动把数压进去 
		while(!ok()&&r<n)
		{
			//压入r这个数
			while(maxh<maxt&&arr[maxdeque[maxt-1]].second<=arr[r].second)
			maxt--;
			maxdeque[maxt++]=r;
			while(minh<mint&&arr[mindeque[mint-1]].second>=arr[r].second)
			mint--;
			mindeque[mint++]=r++;
		}
		if(ok())
		{
			ans=min(ans,arr[r-1].first-arr[l].first);
		}
		//把l处的数弹出
		if(minh<mint&&mindeque[minh]==l)
		minh++;
		if(maxh<maxt&&maxdeque[maxh]==l)
		maxh++; 
	}
	cout<<(ans==INT_MAX?-1:ans);
 } 

单调队列的其他用法

862. 和至少为 K 的最短子数组 - 力扣(LeetCode)

相对于每一个i最短能延伸到多短能让子数组和大于k

用一个从小到大组织的单调队列,当前的数和队头的数差大于k时候就让他走出队列,要满足这个且足够近

class Solution {
public:
    static const int maxn=100001;
    long long sum[maxn];
    int deque[maxn],h,t;
    int shortestSubarray(vector<int>& nums, int k) {
        //构建前缀和
        for(int i=0;i<nums.size();i++)
        sum[i+1]+=sum[i]+nums[i];
        int ans=INT_MAX;
        for(int i=0;i<=nums.size();i++)
        {
            //结算答案弹出头部不会再被算的答案
            while(h<t&&sum[i]-sum[deque[h]]>=k)
            ans=min(ans,i-deque[h++]);
            //塞入当前值
            while(h<t&&sum[i]<=sum[deque[t-1]])
            t--;
            deque[t++]=i;
        }
        return ans==INT_MAX?-1:ans;
    }
};

满足不等式的最大值

1499. 满足不等式的最大值 - 力扣(LeetCode)

我们假设j>i也就是从后往前看也就是要看前面xj-xi+yj+yi的最大值,

说白了也就是看前面的yi减xi那个更大一点

所以我们队列要存放两个数,一个i,一个yi-xi

class Solution {
public:
    static const int maxn=100001;
    int deque[maxn][2];
    int h,t;
    int ans=INT_MIN;
    int findMaxValueOfEquation(vector<vector<int>>& points, int k) {
       for(int i=0;i<points.size();i++)
       {
          int x=points[i][0];
          int y=points[i][1];
          //先去掉不满足距离<=k的答案
          while(h<t&&deque[h][0]+k<x)
          h++;
          //结算答案
          if(h<t)
          ans=max(ans,x+y+deque[h][1]-deque[h][0]);
          //压进队列
          while(h<t&&deque[t-1][1]-deque[t-1][0]<=y-x)
          t--;
          deque[t][0]=x;
          deque[t++][1]=y;
       } 
       return ans;
    }
};

你可以安排的最多任务数目

二分答案:最多安排的任务当然是工人和任务数的最小值,最少的是0

先都排好序

如果不吃药有任务做,把所有能做的任务放到队列,然后去做里面最小消耗的任务

如果队列里没任务做了,就去吃药,然后选任务,去做里面最大的任务,不能让吃的药浪费

class Solution {
public:
    int deque[50001];
    int h,t;
    vector<int> t1,w1;
    int maxTaskAssign(vector<int>& tasks, vector<int>& workers, int pills, int strength) {
    sort(tasks.begin(),tasks.end());
    sort(workers.begin(),workers.end());
    t1=tasks;
    w1=workers;
    int tsize=tasks.size(),wsize=workers.size();
    int ans=0;
    for(int l=0,r=min(tsize,wsize),m;l<=r;)  
    {
        m=((l+r)>>1);//检查能不能完成这么多任务,选了最小的m个任务
        if(f(0,m-1,wsize-m,wsize-1,strength,pills))
        {
            ans=m;
            l=m+1;
        }
        else
        r=m-1;
    }
    return ans;
    }
    bool f(int tl,int tr,int wl,int wr,int strength,int pills)
    {
        h=t=0;
        int cnt=0;
        //每次遍历每个工人
        for(int i=wl,j=tl;i<=wr;i++)
        {
             for(;j<=tr&&t1[j]<=w1[i];j++)
             deque[t++]=j;
             if(h<t&&t1[deque[h]]<=w1[i])//队列有东西并且这个活能干
             {
                h++;
             }
             else
             {
                //吃了药,去检查是否能够把一些任务塞进队列
                 for(;j<=tr&&t1[j]<=w1[i]+strength;j++)
                 deque[t++]=j;
                 if(h<t)
                 {
                    cnt++;
                    t--;//做最后一个任务
                 }
                 else
                 {
                    return false;//吃了药还是一个也做不了,说明完不成了
                 }
             }
        }
        return cnt<=pills;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值