zoukankan      html  css  js  c++  java
  • 队列:最近的请求次数 (Leetcode 933 / Leetcode 232 / 剑指09 / Leetcode 225 / Leetcode 862 )

     

     

     

     

     

     

     

    /**
    * 暴力解法
    * 1.创建数组,存放所有的请求
    * 整型数组,存放10000个元素
    * 2.把当前请求存入数组
    * 记录最后一次存入的索引,从0开始
    * 3.统计距离此次请求前3000毫秒之间的请求次数
    * 从最后一次存放位置倒序遍历
    *
    * @param t 时长为 t(单位:毫秒) 的请求
    * @return 过去3000毫秒内有多少次请求:[t-3000, t]
    */
    public int ping(int t) {
      // 2.把当前请求存入数组
      int end = 0;
      for (int i = 0; i < array.length; i++) {
        if (array[i] == 0) { // 细节:数组元素是0,该位置没有存过请求
          array[i] = t;
          end = i; // 记录最近一次请求存放的索引
          break;
        }
      }
      // 3.统计距离此次请求前3000毫秒之间的请求次数
      int count = 0; // 计数器
      while (array[end] >= t - 3000) {
        count++;
        if (--end < 0) { // 防止越界
          break;
        }
      }
      return count; 
    }

     

     

     

    // 1.创建数组,存放所有的请求
    // int[] array = new int[10000];
    int[] array = new int[3002];
    // 2.记录起止索引,从0开始
    int start = 0, end = 0;
    /**
    * 优化解法:双指针
    * 1.创建数组存放请求:int[3002]
    * 2.额外定义开始指针
    * start=0,end=0,记录起止索引
    * 3.存放请求后,更新起止索引
    * end++; 从上次的开始索引(start)向后查找
    * 直到新的合法的起始位置
    * 4.通过end与start差值计算请求次数
    *
    * @param t 时长为 t(单位:毫秒) 的请求
    * @return 过去3000毫秒内有多少次请求:[t-3000, t]
    */
    public int ping(int t) {
      // 3.存放请求后,更新起止索引
      array[end++] = t; // 存放最近一次请求,结束索引加 1
      end = end == array.length ? 0 : end; // 越界后,从0开始
      // 从start位置开始,正向查找符合要求的请求次数
      while (array[start] < t - 3000) { // 过滤掉所有不符合要求的数据
        start ++;
        start = start == array.length ? 0 : start;
      }
      // 4.通过end与start差值计算请求次数
      if (start > end) { // 请求次数超过数组容量,发生了溢出
        return array.length - (start - end);
      }
      // 此时,end为最新一次请求 + 1 的索引,start是3000毫秒前的第一次合法请求索引
      return end - start;
    }

     

     

     

     

     

     

    /**
    * 最优解:队列解法
    * 1.使用链表实现一个队列
    * 定义属性:队头-head、队尾-tail、长度-size
    * 定义方法:添加节点-add(int)、移除节点-poll() 、队列长度-size()
    * 定义内部类:Node,封装每次入队的请求数据和指向下一个节点的指针
    * 2.每次请求向队列尾部追加节点
    * 3.循环检查队头数据是否合法
    * 不合法则移除该节点
    * 4.返回队列长度
    * @param t
    * @return
    */
    public int ping(int t) {
      // 2.每次请求向队列尾部追加节点
      q.add(t);
      // 3.循环检查队头数据是否合法
      while (q.head.getVal() < t - 3000){
        q.poll();
      }   
    // 4.返回队列长度   return q.size(); }
    /**
    * 最优解:队列解法
    * 1.使用链表实现一个队列
    * 定义属性:队头-head、队尾-tail、长度-size
    * 定义方法:添加节点-add(int)、移除节点-poll() 、队列长度-size()
    * 定义内部类:Node,封装每次入队的请求数据和指向下一个节点的指针
    * 2.每次请求向队列尾部追加节点
    * 3.循环检查队头数据是否合法
    * 不合法则移除该节点
    * 4.返回队列长度
    * @param t
    * @return
    */
    public int ping(int t) {
      // 2.每次请求向队列尾部追加节点
      q.add(t);
      // 3.循环检查队头数据是否合法
      while (q.head.getVal() < t - 3000){
        q.poll();
      }   
    // 4.返回队列长度   return q.size(); }

    队列实现代码:

    Queue q;
    public RecentCounter() {
      q = new Queue();
    }
    class Queue { // 1.使用链表实现一个队列
      Node head;
      Node tail;
      int size = 0;
      public Queue() {
      }
      public void add(int x) { // 向尾部添加一个节点
        Node last = tail;
        Node newNode = new Node(x);
        tail = newNode; // 尾指针指向新节点
        if (last == null) { // 第一次添加数据
          head = newNode;
          tail = newNode;
        } else {
          last.next = newNode; // 前一个节点指向新节点
        }
        size++; // 每添加一个节点,队列长度+1
      }
      public int poll() { // 从头部移除一个节点
        int headVal = head.val; // 获取头节点的数据
        Node next = head.next;
        head.next = null; // 链表第一个节点断开
        head = next; // head指针指向后一个节点
        if (next == null) { // 队列中的最后一个元素
          tail = null; // 处理尾指针
        }
        size--; // 每移出一个节点,队列长度减1
        return headVal;
      }
      public int size() {
        return size;
      }
      class Node { // 队列节点:链表结构
        int val;
        Node next;
        Node(int x) {
          val = x;
        }
        int getVal() {
          return val;
        }
      }
    }

    辅助数据结构:队列。代码如下:

    class Queue { // 1.使用链表实现一个队列
      Node head;
      Node tail;
      int size = 0;
      public Queue() {
      }
      public void add(int x) {}
      public int poll() {}
      public int size() {}
      class Node { // 队列节点:链表结构
        int val;
        Node next;
        Node(int x) {
          val = x;
        }
        int getVal() {
          return val;
        }
      }
    }
    输入:inputs = [[1],[100],[3001],[3002]]
    输出:[1,2,3,3]

     

     

     1/4 用栈实现队列

      

    请你仅使用两个栈实现先入先出队列。队列应当支持一般队列的支持的所有操作(push、pop、peek、empty):

    实现 MyQueue 类:

      void push(int x) 将元素 x 推到队列的末尾
      int pop() 从队列的开头移除并返回元素
      int peek() 返回队列开头的元素
      boolean empty() 如果队列为空,返回 true ;否则,返回 false

    说明:

      你只能使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
      你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

    进阶:

      你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n) ,即使其中一个操作可能花费较长时间。

    示例:

    输入:
      ["MyQueue", "push", "push", "peek", "pop", "empty"]
      [[], [1], [2], [], [], []]
    输出:
      [null, null, null, 1, 1, false]

    解释:
      MyQueue myQueue = new MyQueue();
      myQueue.push(1); // queue is: [1]
      myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
      myQueue.peek(); // return 1
      myQueue.pop(); // return 1, queue is [2]
      myQueue.empty(); // return false

    提示:

      1 <= x <= 9
      最多调用 100 次 push、pop、peek 和 empty
      假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)

    /**
     * 入队(push):
     * 新元素总是压入 s1 的栈顶,同时我们会把 s1 中压入的第一个元素赋值给作为队首元素的 front 变量
     * 出队(pop):
     * 根据栈 LIFO 的特性,s1 中第一个压入的元素在栈底。为了弹出 s1 的栈底元素,我们得把 s1 中所有的元素全部弹出,
     * 再把它们压入到另一个栈 s2 中,这个操作会让元素的入栈顺序反转过来。通过这样的方式,s1 中栈底元素就变成了 s2 的栈顶元素,
     * 这样就可以直接从 s2 将它弹出了。一旦 s2 变空了,我们只需把 s1 中的元素再一次转移到 s2 就可以了
     * 
     * 入队 - O(1),出队 - 摊还复杂度 O(1)
     */
    public class MyQueue {
        private Stack<Integer> s1;//入栈
        private Stack<Integer> s2;//出栈
        //构造方法
        public MyQueue(){
            s1 = new Stack<>();
            s2 = new Stack<>();
        }
    
        //入栈
        public void push(int x) {
        /*
        1.首先给s1入栈
         */
            s1.push(x);
        }
    
        public int pop() {
        /*
        1.如果s2为空,则将s1(是否为空)全部的值先移动到s2
        2.如果s2有值,则直接弹出
         */
            if (s2.empty()){
                while(!s1.empty()){
                    s2.push(s1.pop());
                }
            }
            //这个判断条件就是去除s1为空,从而导致s2也为空的情况
            if (!s2.empty()){
                return s2.pop();
            }
            return -1;
        }
    
        public int peek() {
            if (s2.empty()){
                while(!s1.empty()){
                    s2.push(s1.pop());
                }
            }
            if (!s2.empty()){
                return s2.peek();
            }
            return -1;
    
        }
    
        public boolean empty() {
            if (s1.empty() && s2.empty()){
                return true;
            }
            return false;
        }
    }
    
    
    /**
     * Your MyQueue object will be instantiated and called as such:
     * MyQueue obj = new MyQueue();
     * obj.push(x);
     * int param_2 = obj.pop();
     * int param_3 = obj.peek();
     * boolean param_4 = obj.empty();
     */
    2/4 用两个栈实现队列

    用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,

    分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )



    示例 1:

    输入:
      ["CQueue","appendTail","deleteHead","deleteHead"]
      [[],[3],[],[]]
    输出:

      [null,null,3,-1]


    示例 2:

    输入:
      ["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
      [[],[],[5],[2],[],[]]
    输出:

      [null,-1,null,null,5,2]
    提示:

      1 <= values <= 10000
      最多会对 appendTail、deleteHead 进行 10000 次调用

    // 思路同上一个题
    public class CQueue {
        Stack<Integer> stack1;
        Stack<Integer> stack2;
    
        public CQueue() {
            stack1 = new Stack<>();
            stack2 = new Stack<>();
        }
    
        public void appendTail(int value) {
            stack1.push(value);
        }
    
        public int deleteHead() {
            // 如果第二个栈为空,把第一个栈中的数据一次入栈到第二个栈
            if (stack2.isEmpty()) {
                while (!stack1.isEmpty()) {
                    stack2.push(stack1.pop());
                }
            }
            //从第二个栈中出数据
            if (stack2.isEmpty()) {
                return -1;
            } else {
                int deleteItem = stack2.pop();
                return deleteItem;
            }
        }
    }

    3/4 用队列实现栈

    使用队列实现栈的下列操作:

      push(x) -- 元素 x 入栈
      pop() -- 移除栈顶元素
      top() -- 获取栈顶元素
      empty() -- 返回栈是否为空
    注意:

      你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。
      你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
      你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。

    /**
     * 入栈操作时,首先获得入栈前的元素个数 n,然后将元素入队到队列,
     * 再将队列中的前 n个元素(即除了新入栈的元素之外的全部元素)依次出队并入队到队列,
     * 此时队列的前端的元素即为新入栈的元素,且队列的前端和后端分别对应栈顶和栈底。
     *
     * 由于每次入栈操作都确保队列的前端元素为栈顶元素,因此出栈操作和获得栈顶元素操作都可以简单实现。
     * 出栈操作只需要移除队列的前端元素并返回即可,获得栈顶元素操作只需要获得队列的前端元素并返回即可(不移除元素)。
     * 由于队列用于存储栈内的元素,判断栈是否为空时,只需要判断队列是否为空即可。
     */
    
    public class MyStack {
        Queue<Integer> queue;
    
        /** 初始化数据结构. */
        public MyStack() {
            queue = new LinkedList<Integer>();
        }
    
        public void push(int x) {
            int n = queue.size();
            queue.offer(x);
            for (int i = 0; i < n; i++) {
                queue.offer(queue.poll());
            }
        }
    
        public int pop() {
            return queue.poll();
        }
    
        public int top() {
            return queue.peek();
        }
    
        public boolean empty() {
            return queue.isEmpty();
        }
    }
    
    /**
     * Your MyStack object will be instantiated and called as such:
     * MyStack obj = new MyStack();
     * obj.push(x);
     * int param_2 = obj.pop();
     * int param_3 = obj.top();
     * boolean param_4 = obj.empty();
     */
    4/4 和至少为 K 的最短子数组

    返回 A 的最短的非空连续子数组的长度,该子数组的和至少为 K 。

    如果没有和至少为 K 的非空子数组,返回 -1 。



    示例 1:

      输入:  A = [1], K = 1
      输出:  1


    示例 2:

      输入:  A = [1,2], K = 4
      输出:  -1
    示例 3:  

      输入:  A = [2,-1,2], K = 3
      输出:  3

    提示:

      1 <= A.length <= 50000
      -10 ^ 5 <= A[i] <= 10 ^ 5
      1 <= K <= 10 ^ 9

    //通过维持一个队列,从头到尾遍历。对于每个y,找出满足,P[y]-P[x]>=K的最大的x。如果y-x比之前的长度要小就记录新的最小值
    class Solution {
        //先计算前缀和p数组
        // 通过维持一个队列,从头到尾遍历。对于每个y,找出满足,p[y]-p[x]>=K的最大的x。
        // 计算的就是 ((0...y-1) - (0....x-1) )=(x....y-1)  长度为y-1-x+1=y-x
        // 如果y-x比之前的长度要小就记录新的最小值
        // 细节问题:要注意负数的情况
        public int shortestSubarray(int[] A, int K) {
            int size = A.length;
            long[] p = new long[size + 1]; //前缀和preSum
            //一次累加每一个值 p[y]存放的是 A数组中从0加到y-1位置
            for (int i = 0; i < size; ++i)
                p[i + 1] = p[i] + (long) A[i];
    
            int result = size + 1;//最小长度
            Deque<Integer> queue = new LinkedList();//存放下标 滑动窗口
    
            for (int y = 0; y < p.length; y++) {         
                //p[y]表示A[y]之前的数字和(A数组中从0加到y-1位置)
                while (!queue.isEmpty() && p[y] <= p[queue.getLast()]) {//存在负数的情况,在队列尾部删除元素
                    queue.removeLast();
                }
                //当前队列头到y的范围内元素和大于K,需要判断是否比之前的长度更小
                while (!queue.isEmpty() && p[y] >= p[queue.getFirst()] + K) {    
                    result = Math.min(result, y - queue.removeFirst());
                }
                //加到队列尾部
                queue.addLast(y);
            }
    
            return result < size + 1 ? result : -1;
        }
    
        //滑动窗口
        public int shortestSubarray2(int[] A, int K) {
            int size = A.length;
            int left = 0, right = 0; //双指针
            int sum = 0;//当前子序列和
            int result = size + 1;
    
            while (right < size) {//右指针未到边界
                sum = sum + A[right];//累加
    
                if (sum <= 0) {//出现负值的情况,双指针都后移到当前右指针的下一个位置
                    left = right + 1;
                    right = right + 1;
                    sum = 0;
                    continue;
                }
    
                int i = right;
                while (A[i] < 0) {//将负数变为0,同时在上一个元素加上这负数
                    A[i - 1] += A[i];
                    A[i] = 0;
                    i--;
                }
    
                if (sum >= K) {//尝试缩小窗口
                    while (left <= right && sum - A[left] >= K) {
                        sum = sum - A[left];
                        left++;
                    }
    
                    if (right - left + 1 < result) {
                        result = right - left + 1;
                        if (result == 1) { //最短为1个,发现最短直接返回,提前终止
                            return result;
                        }
                    }
                }
                right++;
    
            }
            return result < size + 1 ? result : -1;
        }
    }
  • 相关阅读:
    Neo.Geo系统视频硬件结构模拟 v2.0
    [原创] CPS1模拟器开发日志
    在博客园发现恶意群体回复打广告的
    [原创] Neo.Geo系统视频硬件结构模拟
    在 ASP.NET 中执行 URL 重写(读书笔记)
    c#中什么情况下用(int)什么情况下用Convert.ToInt32
    ASP.NET 例程完全代码版(7)——2.0中实现自配置的成员角色管理库
    Request.UrlReferrer详解
    .NET中获取电脑名、IP及用户名方法
    ASP.NET 2.0中的跨页面提交
  • 原文地址:https://www.cnblogs.com/JasperZhao/p/15087462.html
Copyright © 2011-2022 走看看