六月集训(16)队列

1.LeetCode:933. 最近的请求次数

原题链接


        写一个 RecentCounter 类来计算特定时间范围内最近的请求。

        请你实现 RecentCounter 类:

        RecentCounter() 初始化计数器,请求数为 0 。

        int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。

        保证 每次对 ping 的调用都使用比之前更大的 t 值。

        示例 1:

        输入:

        [“RecentCounter”, “ping”, “ping”, “ping”, “ping”]

        [[], [1], [100], [3001], [3002]]

        输出:

        [null, 1, 2, 3, 3]

        返回 3

        recentCounter.ping(3002); // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3

        提示:

        1 <= t <= 10^9

        保证每次对 ping 调用所使用的 t 值都 严格递增

        至多调用 ping 方法 10^4 次


        简单的队列应用题,对于每个ping操作去检查队首是否是当前时间的3000ms之前的时间,如果是就删除队首到t的3000ms之内的时间。最后返回队列的长度。

class RecentCounter {
    queue<int> q;
public:
    RecentCounter() {
        while(!q.empty()){
            q.pop();
        }
    }
    
    int ping(int t) {
        q.push(t);
        while(!q.empty()&&q.front()<t-3000){
            q.pop();
        }
        return q.size();
    }
};

2.LeetCode:2073. 买票需要的时间

原题链接


        有 n 个人前来排队买票,其中第 0 人站在队伍 最前方 ,第 (n - 1) 人站在队伍 最后方 。

        给你一个下标从 0 开始的整数数组 tickets ,数组长度为 n ,其中第 i 人想要购买的票数为 tickets[i] 。

        每个人买票都需要用掉 恰好 1 秒 。一个人 一次只能买一张票 ,如果需要购买更多票,他必须走到 队尾 重新排队(瞬间 发生,不计时间)。如果一个人没有剩下需要买的票,那他将会 离开 队伍。

        返回位于位置 k(下标从 0 开始)的人完成买票需要的时间(以秒为单位)。

        示例 1:

        输入:tickets = [2,3,2], k = 2

        输出:6

        示例 2:

        输入:tickets = [5,1,1,1], k = 0

        输出:8

        提示:

        n == tickets.length

        1 <= n <= 100

        1 <= tickets[i] <= 100

        0 <= k < n


        也是类似于循环排队的问题,这里终止的条件是下标为k的元素的tickets为0即可返回他所用的时间。

        我们利用结构体数组来模拟队列,front为队首,rear为队尾,当front<rear的时候我们就继续遍历下去也相当于队列不为空,每次模拟到一个元素时间加一,并且观察当前元素的票数是否为0,如果不是入队否则观察他的原始下表是否为k,如果是直接返回,否则继续遍历。

typedef struct {
    int idx;//原始下标
    int need;//需要的票数
}Q;

int timeRequiredToBuy(int* tickets, int ticketsSize, int k){
    Q q[15000],tmp;
    int i=0;
    int front=0,rear=0;
    for(int i=0;i<ticketsSize;i++){
        q[rear].idx=i;
        q[rear].need=tickets[i];
        rear++;
    }
    int time=0;
    while(front<rear)
    {
        tmp=q[front++];
        tmp.need--;
        time++;
        if(tmp.need){
            q[rear++]=tmp;
        }
        else if(tmp.idx==k){
            return time;
        }
    }
    return 0;
}

3.LeetCode:1700. 无法吃午餐的学生数量

原题链接


        学校的自助午餐提供圆形和方形的三明治,分别用数字 0 和 1 表示。所有学生站在一个队列里,每个学生要么喜欢圆形的要么喜欢方形的。

        餐厅里三明治的数量与学生的数量相同。所有三明治都放在一个 栈 里,每一轮:

        如果队列最前面的学生 喜欢 栈顶的三明治,那么会 拿走它 并离开队列。

        否则,这名学生会 放弃这个三明治 并回到队列的尾部。

        这个过程会一直持续到队列里所有学生都不喜欢栈顶的三明治为止。

        给你两个整数数组 students 和 sandwiches ,其中 sandwiches[i] 是栈里面第 i​​​​​​ 个三明治的类型(i = 0 是栈的顶部), students[j] 是初始队列里第 j​​​​​​ 名学生对三明治的喜好(j = 0 是队列的最开始位置)。请你返回无法吃午餐的学生数量。

        示例:

        输入:students = [1,1,1,0,0,1], sandwiches = [1,0,0,0,1,1]

        输出:3

        提示:

        1 <= students.length, sandwiches.length <= 100

        students.length == sandwiches.length

        sandwiches[i] 要么是 0 ,要么是 1 。

        students[i] 要么是 0 ,要么是 1 。


        这道题做法类似循环排队的做法,不但用学生数组去和三明治数组匹配,如果匹配不成功就让他到队尾继续排队,当学生数组到尾的时候即说明匹配成功。这种情况是很好理解的。但是当匹配不成功的时候就会出现循环排队的情况,这就需要我们人工设置一个最大匹配次数,这个次数最好能将队伍进行匹配数次,当超过了这个最大次数我们返回false即可。

class Solution {
public:
    int countStudents(vector<int>& students, vector<int>& sandwiches) {
        int aindex=0,sindex=0;
        int limit=0;
        while(sindex<students.size( ) && aindex < sandwiches.size()){
            if(students[sindex]==sandwiches[aindex]){
                ++sindex,++aindex;
            }else {
                students.push_back(students[sindex]);
                sindex++;
                if(limit++>200){
                    break;
                }
            }
        }
        return students.size()-sindex;
    }
};

4.LeetCode:239. 滑动窗口最大值

原题链接


        给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

        返回 滑动窗口中的最大值 。

        示例 1:

        输入:nums = [1,3,-1,-3,5,3,6,7], k = 3

        输出:[3,3,5,5,6,7]

        示例 2:

        输入:nums = [1], k = 1

        输出:[1]

        提示:

        1 <= nums.length <= 1e5

        -1e4 <= nums[i] <= 1e4

        1 <= k


        这道题的做法有两种,一是利用优先级队列,不断维护窗口,在滑动窗口的过程中我们每次加入右边界的时候不需要考虑队列中元素数量的问题,因为我们想要的仅仅是该窗口的最大值 ,所以这种做法只需要保证堆顶元素的位于窗口内部即可。这种做法易于理解不过时间复杂度为O(logN)

class Solution {
public:
    priority_queue<pair<int,int>> q;
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n=nums.size();
        int l=0,r=0;
        vector<int> ans;
        for(int i=0;i<k;i++){
            q.push(make_pair(nums[i],i));
        }
        ans.push_back(q.top().first);
        for(int i=1;i<=n-k;i++){
            q.push((make_pair(nums[i+k-1],i+k-1)));
            while(q.top().second<i){
                q.pop();
            }
            ans.push_back(q.top().first);
        }
        return ans;
    }
};


        另一种做法是时间复杂度为O(N)的做法:利用双端队列。这里我们在队列中维护窗口的最大值,现在我们考虑构造窗口的过程。

        在加入新元素的时候首先需要保证队列中元素均位于当前窗口内,所以先对队首元素进行下标判断。之后我们想要加入新元素,但是由于我们想要的仅仅是该窗口的最大值,既然之前我们已经保证了队列元素均位于窗口内部,现在我们就可以进行元素的移除操作。

        如何进行移除呢?也很简单,这里构造一个单调递减队列。每次加入新元素我们去查看当前元素与队尾元素的大小关系,如果当前元素比队尾大就删除队尾直到队列为空或者队尾元素不再小于他。那么这样我们的队首就是当前窗口的最大值,在答案数组中加入他。

        这样做的意义在于我们在整个过程中都相当于给每个窗口内部元素按照降序排序,并移除了我们不需要的元素。当寻找下一个窗口的最值时,由于会先把不属于窗口的元素移除,并且由于之前的窗口是合法的,那么队列中剩下的元素就会是之前窗口与当前窗口的相交部分,他们也是按照降序排列的。这也在加入新元素的时候我们就保证了合法性。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        deque<int> q;
        vector<int> ans(nums.size()-k+1,0);
        for(int i=0;i<nums.size();++i){
            while(!q.empty()&&i-q.front()+1>k){
                q.pop_front();
            }
            while(!q.empty()&&nums[q.back()]<=nums[i]){
                q.pop_back();
            }
            q.push_back(i);
            if(i>=k-1){
                ans[i-k+1]=nums[q.front()];
            }
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值