五月训练 Day16

0. Leetcode 933. 最近的请求次数

写一个 RecentCounter 类来计算特定时间范围内最近的请求。
请你实现 RecentCounter 类:
RecentCounter() 初始化计数器,请求数为 0 。
int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。
保证 每次对 ping 的调用都使用比之前更大的 t 值。

分析与解答

使用队列,放入新的数时对队首元素进行判断,若不满足条件则弹出,将队列大小计入结果中:

class RecentCounter {
    deque<int> deq;
    
public:
    int ping(int t) {
        deq.push_back(t);
        while (deq.front() < t - 3000) {
            deq.pop_front();
        }
        
        return deq.size();
    }
};
/**
 * Your RecentCounter object will be instantiated and called as such:
 * RecentCounter* obj = new RecentCounter();
 * int param_1 = obj->ping(t);
 */

1. Leetcode 2073. 买票需要的时间

有 n 个人前来排队买票,其中第 0 人站在队伍 最前方 ,第 (n - 1) 人站在队伍 最后方 。
给你一个下标从 0 开始的整数数组 tickets ,数组长度为 n ,其中第 i 人想要购买的票数为 tickets[i] 。
每个人买票都需要用掉 恰好 1 秒 。一个人 一次只能买一张票 ,如果需要购买更多票,他必须走到 队尾 重新排队(瞬间 发生,不计时间)。如果一个人没有剩下需要买的票,那他将会 离开 队伍。
返回位于位置 k(下标从 0 开始)的人完成买票需要的时间(以秒为单位)。

分析与解答

可以使用模拟法,用队列模拟买票过程,买票时从队首弹出,计数 -1,若计数大于 0,向队尾插入即可。注意这里需要记录每个人在原来队列的位置,因此队列中的元素为 <票数,位置> 数对:

class Solution {
public:
    int timeRequiredToBuy(vector<int>& tickets, int k) {
        int result(0);
        deque<std::pair<int, int>> deq;
        for (int i = 0; i < tickets.size(); ++i) {
            deq.push_back(make_pair(tickets[i], i));
        }
        
        while (get<1>(deq.front()) != k || 
               get<1>(deq.front()) == k && get<0>(deq.front()) != 1) {
                   get<0>(deq.front())--;
                   auto ele = deq.front();
                   
                   if (get<0>(deq.front()) != 0) {
                       deq.pop_front();
                       deq.push_back(ele);
                   } else {
                       deq.pop_front();
                   }
                   result++;
               }
        
        return result + 1;
    }
};

2. Leetcode 641. 设计循环双端队列

设计实现双端队列。
实现 MyCircularDeque 类:
MyCircularDeque(int k) :构造函数,双端队列最大为 k 。
boolean insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true ,否则返回 false 。
boolean insertLast() :将一个元素添加到双端队列尾部。如果操作成功返回 true ,否则返回 false 。
boolean deleteFront() :从双端队列头部删除一个元素。 如果操作成功返回 true ,否则返回 false 。
boolean deleteLast() :从双端队列尾部删除一个元素。如果操作成功返回 true ,否则返回 false 。
int getFront() ):从双端队列头部获得一个元素。如果双端队列为空,返回 -1 。
int getRear() :获得双端队列的最后一个元素。 如果双端队列为空,返回 -1 。
boolean isEmpty() :若双端队列为空,则返回 true ,否则返回 false 。
boolean isFull() :若双端队列满了,则返回 true ,否则返回 false 。

分析与解答

设计题,按照题目要求进行设计即可。除了使用循环数组,也可以用提前分配好空间的数组进行模拟:

class MyCircularDeque {
    int* data; // 存储数据
    int head; // 当前第一个元素下标
    int tail; // 当前最后一个元素下标
    int maxDataSize; // 最大数据数量
    
public:
    MyCircularDeque(int k) {
        data = new int[k];
        head = -1;
        tail = -1;
        maxDataSize = k;
    }
    
    bool insertFront(int value) {
        if (head < 0) {
            data[0] = value;
            head = 0;
            tail = 0;
            return true;
        } else {
            int curHead(head);
            head--;
            if (head < 0) {
                head = maxDataSize + head;
            }
            if (head != tail) {
                data[head] = value;
                return true;
            } else {
                head = curHead;
                std::cout << head << std::endl;
                return false;
            }
        }
    }
    
    bool insertLast(int value) {
        if (tail < 0) {
            data[0] = value;
            head = 0;
            tail = 0;
            return true;
        } else {
            int curTail(tail);
            tail++;
            if (tail >= maxDataSize) {
                tail = tail % maxDataSize;
            }
            if (head != tail) {
                data[tail] = value;
                return true;
            } else {
                tail = curTail;
                return false;
            }
        }
    }
    
    bool deleteFront() {
        if (head < 0) {
            return false;
        }
        
        if (head == tail) { // 只有一个元素
            head = -1;
            tail = -1;
            return true;
        }
        head++;
        if (head >= maxDataSize) {
            head = head % maxDataSize;
        }
        
        return true;
    }
    
    bool deleteLast() {
        if (tail < 0) {
            return false;
        }
        
        if (head == tail) { // 只有一个元素
            head = -1;
            tail = -1;
            return true;
        }
        tail--;
        if (tail < 0) {
            tail = maxDataSize + tail;
        }
        
        return true;
    }
    
    int getFront() {
        if (head < 0) {
            return -1;
        }
        return data[head];
    }
    
    int getRear() {
        if (tail < 0) {
            return -1;
        }
        return data[tail];
    }
    
    bool isEmpty() {
        return head < 0? true: false;
    }
    
    bool isFull() {
        if (head < 0) {
            return false;
        }
        
        int headPos(head);
        head--;
        if (head < 0) {
            head = maxDataSize + head;
        }
        if (head == tail) {
            head = headPos;
            return true;
        } else {
            head = headPos;
            return false;
        }
    }
};

/**
 * Your MyCircularDeque object will be instantiated and called as such:
 * MyCircularDeque obj = new MyCircularDeque(k);
 * boolean param_1 = obj.insertFront(value);
 * boolean param_2 = obj.insertLast(value);
 * boolean param_3 = obj.deleteFront();
 * boolean param_4 = obj.deleteLast();
 * int param_5 = obj.getFront();
 * int param_6 = obj.getRear();
 * boolean param_7 = obj.isEmpty();
 * boolean param_8 = obj.isFull();
 */

3. Leetcode 1670. 设计前中后队列

请你设计一个队列,支持在前,中,后三个位置的 push 和 pop 操作。
请你完成 FrontMiddleBack 类:
FrontMiddleBack() 初始化队列。
void pushFront(int val) 将 val 添加到队列的 最前面 。
void pushMiddle(int val) 将 val 添加到队列的 正中间 。
void pushBack(int val) 将 val 添加到队里的 最后面 。
int popFront() 将 最前面 的元素从队列中删除并返回值,如果删除之前队列为空,那么返回 -1 。
int popMiddle() 将 正中间 的元素从队列中删除并返回值,如果删除之前队列为空,那么返回 -1 。
int popBack() 将 最后面 的元素从队列中删除并返回值,如果删除之前队列为空,那么返回 -1 。
请注意当有 两个 中间位置的时候,选择靠前面的位置进行操作。比方说:
将 6 添加到 [1, 2, 3, 4, 5] 的中间位置,结果数组为 [1, 2, 6, 3, 4, 5] 。
从 [1, 2, 3, 4, 5, 6] 的中间位置弹出元素,返回 3 ,数组变为 [1, 2, 4, 5, 6] 。

分析与解答

设计时可以用两个队列模拟,让中间数始终保持在第一个队列的最后一个位置。这里我们直接用数组进行模拟:

class FrontMiddleBackQueue {
    int data[2010]; // 存储数据
    int head; // 头插入时使用的位置
    int tail; // 尾插入时使用的位置
    int dataSize; // 数据大小

public:
    FrontMiddleBackQueue() {
        head = 1004;
        tail = 1005;
        dataSize = 0;
    }
    
    void pushFront(int val) {
        data[head] = val;
        head--;
        dataSize++;
    }
    
    void pushMiddle(int val) {
        int insertPos(0);
        if (dataSize & 0x1 == 1) { // 根据数据大小与头元素位置计算中间元素位置
            // 奇数
            insertPos = head + dataSize / 2 + 1;
        } else {
            insertPos = head + dataSize / 2 + 1;
        }

        for (int i = tail; i >= insertPos + 1; --i) { // 中间位置往后的数据向后移动
            data[i] = data[i - 1];
        }
        data[insertPos] = val;
        dataSize++;
        tail++;
    }
    
    void pushBack(int val) {
        data[tail] = val;
        tail++;
        dataSize++;
    }
    
    int popFront() {
        if (dataSize > 0) {
            int result(data[head + 1]);
            head++;
            dataSize--;
            return result;
        }
        
        return -1;
    }
    
    int popMiddle() {
        if (dataSize == 0) {
            return -1;
        }

        int middlePos(0);
        if (dataSize & 0x1 == 1) {
            // 奇数
            middlePos = head + dataSize / 2 + 1;
        } else {
            middlePos = head + dataSize / 2;
        }
        int result(data[middlePos]);
        for (int i = middlePos; i < tail; ++i) { // 将中间位置往后的数据向前移动
            data[i] = data[i + 1];
        }
        tail--;
        dataSize--;
        
        return result;
    }
    
    int popBack() {
        if (dataSize > 0) {
            int result(data[tail - 1]);
            tail--;
            dataSize--;
            return result;
        }
        
        return -1;
    }
};

/**
 * Your FrontMiddleBackQueue object will be instantiated and called as such:
 * FrontMiddleBackQueue* obj = new FrontMiddleBackQueue();
 * obj->pushFront(val);
 * obj->pushMiddle(val);
 * obj->pushBack(val);
 * int param_4 = obj->popFront();
 * int param_5 = obj->popMiddle();
 * int param_6 = obj->popBack();
 */

总结

队列数据结构并不复杂,要点在于弄清下标含义以及其变化情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值