辅助栈、单调栈与单调队列在lc中的应用

news2024/9/23 19:15:17

为什么要汇总在一块?

三者都有何区别?

总结

1 泛化性更好的策略

个人建议单调栈/队列中存放的元素最好是下标而不是值,因为有的题目需要根据下标计算,这样泛化性更好。参考lc239和lc496

2 单调队列何栈其实可以共用同一套模板

大概的思路是先把不满足单调性的元素poll掉,然后offer一个当前符合条件的元素。参考lc239和lc496

3 单调栈专门解决Next Greater Number问题

1 单调队列

239. 滑动窗口最大值(这个题中要求存储元素下标,因为需要根据下标判断是否在窗口内)

    public int[] maxSlidingWindow(int[] nums, int k) {
        Deque<Integer>q=new ArrayDeque<>();
        int[] res=new int[nums.length-k+1];
        int index=0;
        for(int i=0;i<nums.length;i++){
            while(!q.isEmpty()&&q.peekFirst()<i-k+1){
                q.pollFirst();
            }
            
            while(!q.isEmpty()&&nums[q.peekLast()]<nums[i]){
                q.removeLast();
            }

            q.offer(i);
            if(i>=k-1){
                res[index++]=nums[q.peekFirst()];
            }
        }
        return res;
    }

2023.7 用友提前批秋招笔试

在这里插入图片描述
// 说明:这里使用两个堆来维护最大值和最小值
// 代码简单易懂,笔试时是一个速AC的好方法
// 时间复杂度O(N*logK),比起单调队列的O(N)的复杂度,是差了一些, 另外这道题是力扣

239. 滑动窗口最大值

的变体,本体想到的另一种方法是同时维护最大值,次大值,最小值和次小值,每次滑动时看弹出的那个是否最大/小,否则就看是否次大/小,然后进行相应的替换

import java.util.*;

public class Main{
    public static void main(String[]args){
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();
        int k=sc.nextInt();
        int[]arr=new int[n];
        for(int i=0;i<n;i++){
            arr[i]=sc.nextInt();
        }
        PriorityQueue<Integer>maxH=new PriorityQueue<>((o1,o2)->(o2-o1));
        PriorityQueue<Integer>minH=new PriorityQueue<>((o1,o2)->(o1-o2));

        for(int i=0;i<k;i++){
            maxH.add(arr[i]);
            minH.add(arr[i]);
        }
        int res=maxH.peek()-minH.peek();

        for(int i=k;i<n;i++){
            int pollE=arr[i-k];
            maxH.remove(pollE);
            minH.remove(pollE);
            maxH.add(arr[i]);
            minH.add(arr[i]);
            res=Math.max(res,maxH.peek()-minH.peek());
        }
        System.out.println(res);
    }
}

方法二:(平常要了解,但是笔试不推荐做,不如使用两个堆来得简单)
采用单调队列来做,时间复杂度O(n)

public static void main(String[]args){
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();
        int k=sc.nextInt();
        int[]arr=new int[n];
        for(int i=0;i<n;i++){
            arr[i]=sc.nextInt();
        }
        Deque<Integer>maxQ=new ArrayDeque<>();
        Deque<Integer>minQ=new ArrayDeque<>();
        int res=0;
        for(int i=0;i<n;i++){
            
            while(!maxQ.isEmpty()&&maxQ.peekFirst()<i-k+1){
                maxQ.pollFirst();
            }

            while(!minQ.isEmpty()&&minQ.peekFirst()<i-k+1){
                minQ.pollFirst();
            }
            while(!maxQ.isEmpty()&&arr[maxQ.peekLast()]<arr[i]){
                maxQ.pollLast();
            }
            while(!minQ.isEmpty()&&arr[minQ.peekLast()]>arr[i]){
                minQ.pollLast();
            }
            maxQ.offer(i);
            minQ.offer(i);
            res=Math.max(res,arr[maxQ.peekFirst()]-arr[minQ.peekFirst()]);
        }
        System.out.println(res);
    }

单调队列是一个非常有用的数据结构,可以解决一类特定的问题,主要是和数组中的滑动窗口最值问题有关。以下是几个使用单调队列的LeetCode题目:

  1. LeetCode 239. 滑动窗口最大值:给你一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
  2. LeetCode 155. 最小栈:设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
  3. LeetCode 907. 子数组的最小值之和:给定一个整数数组 arr,找到 min(b) 的总和,其中 b 的范围为 arr 的每个(连续)子数组。
  4. LeetCode 862. 和至少为 K 的最短子数组:返回 A 的最短的非空连续子数组的长度,该子数组的和至少为 K 。如果没有和至少为 K 的非空子数组,返回 -1 。
  5. LeetCode 456. 132 模式:给你一个整数数组 nums ,数组中共有 n 个整数。132 模式的子序列 由三个整数 nums[i]、nums[j] 和 nums[k] 组成,并同时满足:i < j < k 和 nums[i] < nums[k] < nums[j] 。如果 nums 中存在 132 模式的子序列 ,返回 true ;否则,返回 false 。

这些题目都可以使用单调队列来优化时间复杂度。

2 单调栈

496. 下一个更大元素 I

思路:求下一个元素就需要先知道后面的元素才能做判断,由此我们可以先从数组尾部开始向前遍历,,因为求的是更大的元素,后面的元素如果更大则一定是在栈底,所以是一个单调递减栈,如果是栈是单调递减的,则在出入栈的时候顺便可以根据栈中是否为空做出判断,如果为空则说明当前的值没有下一个更大值。

代码:

    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        Map<Integer,Integer>mp=new HashMap<>();
        Deque<Integer>st=new ArrayDeque<>();
        for(int i=nums2.length-1;i>=0;i--){
            int num=nums2[i];
            while(!st.isEmpty()&&num>st.peekLast()){
                st.pollLast();
            }
            
            mp.put(num,st.isEmpty()?-1:st.peekLast());
            st.offerLast(num);
        }
        int[]res=new int[nums1.length];
        for(int i=0;i<nums1.length;i++){
            res[i]=mp.get(nums1[i]);
        }
        return res;

    }
// 栈中存入下标,泛化性更好的答案
class Solution {
    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        HashMap<Integer,Integer> map = new HashMap<>();
        Deque<Integer> stack = new LinkedList<>();
        for(int i=0;i<nums2.length;i++){
            while(!stack.isEmpty()&&nums2[stack.peek()]<nums2[i]){
                int j = stack.pop();
                map.put(nums2[j],nums2[i]); // 此时nums2[j]<nums2[i]
            }
            stack.push(i); // 下标入栈
        }
        int[] ans = new int[nums1.length];
        for(int i=0;i<nums1.length;i++){
            ans[i] = map.getOrDefault(nums1[i],-1);
        }
        return ans;
    }
}

503.下一个更大元素II

class Solution {
    public int[] nextGreaterElements(int[] nums) {
        int n=nums.length;

        Deque<Integer>st=new ArrayDeque<>();
        int[]res=new int[n];

        for(int i=2*n-1;i>=0;i--){
            int newI=i%n;
            while(!st.isEmpty()&&nums[st.peekLast()%n]<=nums[newI]){ //#A
                st.pollLast();
            }
            res[i%n]=st.isEmpty()?-1:nums[st.peekLast()%n];//#B
            st.offerLast(i);
        }

        return res;
    }
}

问:为这里的边界条件中,为什么pop的时候是nums[st.peekLast()%n]<=nums[newI]而不是nums[st.peekLast()%n]<=nums[newI]?

答:因为题目求解的是下一个更大的元素,而不是下一个不比自己小的元素,如果改成了后者,则在#B做判断时,会可能取到一个跟自己一样大的元素。比如 1 2 2,如果使用后者,两个2会连续入栈,否则只能入栈一次。

907. 子数组的最小值之和

正常思路

每遍历一个元素就以当前元素为基点,向左右扩散,找到一个自己是最小值的最长子数组,然后计算这个最长子数组可以分成多少个包含当前元素的子数组,计算个数*最小值然后累加到结果中。

    public int sumSubarrayMins3(int[] arr) {
        int n=arr.length;
        long res=0;
        final int mod=1000000007;
        for(int i=0;i<n;i++){
            int l=i,r=i;
            //找到左边界
            while(l-1>=0&&arr[l-1]>=arr[i]){
                l--;
            }
            //找到右边界,为什么右边界是>而不是>=
            while(r+1<n&&arr[r+1]>arr[i]){
                r++;
            }
            long numSubArr=(i-l+1)*(r-i+1);
            // System.out.println("i:"+i+",l:"+l+",r:"+r+",numSubArr:"+numSubArr+",tmpSum:"+numSubArr*arr[i]);
            res=(res+numSubArr*arr[i])%mod;
        }
        return (int)(res%mod);
    }

单调栈解题

单调栈解决的就是找出”下一个更大值“的问题,在本题中,寻找当前元素的左边界等同于上一个更大值(从左往右遍历),寻找右边界相当于下一个更大值,要求从结尾往前遍历数组

    public int sumSubarrayMins(int[] arr) {
        int n=arr.length;
        //从前往后求上一个更大值
        //递减栈
        Deque<Integer>st1=new ArrayDeque<>();
        int[]l=new int[n];
        for(int i=0;i<n;i++){
            while(!st1.isEmpty()&&arr[st1.peekLast()]>arr[i]){
                st1.pollLast();
            }
            l[i]=i-(st1.isEmpty()?-1:st1.peekLast());
            st1.offerLast(i);
        }
        //从后往前求下一个更大值
        //递减栈
        Deque<Integer>st2=new ArrayDeque<>();
        int[]r=new int[n];
        for(int i=n-1;i>=0;i--){
            while(!st2.isEmpty()&&arr[st2.peekLast()]>=arr[i]){
                st2.pollLast();
            }
            r[i]=(st2.isEmpty()?n:st2.peekLast())-i;
            st2.offerLast(i);
        }
        long res=0;
        final int mod=1000000007;

        for(int i=0;i<n;i++){
            res=(res+(long)arr[i]*r[i]*l[i])%mod;
        }

        return (int)res;
    }

为什么在找边界时,左边界的while循环条件是arr[st2.peekLast()]>arr[i]但是查找右边界时是arr[st2.peekLast()]>=arr[i]?

答:这是为了区分重复的元素的边界,比如4232,如果都是大于号,则子集找不全,如果都是大于等于号,则会有冗余,比如第一个2的所属子集{42,423,23,232,2,4232}和第二个2的所属子集{32, 2,232,4232}的重合子集是{232,4232},但是事实上如果对第一个使用>,第二个使用>=,则子集集合分别为{42,423,23,232,2,4232},{32,2}

1118.一月有多少天

3 辅助栈

辅助栈解法 155. 最小栈

class MinStack {
    Deque<Integer>st;
    Deque<Integer>q;

    public MinStack() {
        st=new ArrayDeque<>();
        q=new ArrayDeque<>();
        
    }
    
    public void push(int val) {
        st.offerLast(val);

        if(q.isEmpty()||val<q.peekLast()){
            q.offerLast(val);
        }else{
            q.offerLast(q.peekLast());
        }
    }
    
    public void pop() {
        st.pollLast();
        q.pollLast();

    }
    
    public int top() {
        return st.peekLast();
    }
    
    public int getMin() {
        return q.peekLast();
    }
}

类似于辅助栈的解法

辅助栈的思想无非就是将每个状态下的最小值存储了起来,我们可以使用类或结构体等自定义类型作为栈的元素,在这个元素中,我们记录当前状态下存储最小值的元素的下标,这样我们就不需要辅助栈了

class MinStack {
    class Node{
        int val;
        int minIndex;
        Node(int val,int minIndex){
            this.val=val;
            this.minIndex=minIndex;
        }
        Node(int val){
            this.val=val;
        }
    }
    List<Node> stack;

    public MinStack() {
        stack=new ArrayList<>();
    }

    public void push(int val) {
        if (stack.isEmpty()){
            stack.add(new Node(val, 0));
        }
        else {
            int currMinIndex=stack.get(stack.size()-1).minIndex;
            int minIndex;
            if (stack.get(currMinIndex).val>val){
                minIndex=stack.size();
            }
            else {
                minIndex=currMinIndex;
            }
            stack.add(new Node(val,minIndex));
        }
    }

    public void pop() {
        stack.remove(stack.size()-1);
    }

    public int top() {
        return stack.get(stack.size()-1).val;
    }

    public int getMin() {
        return stack.get(stack.get(stack.size()-1).minIndex).val;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/818026.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【个人笔记】Linux 服务管理两种方式service和systemctl

service命令与systemctl 命令 service 命令与传统的 SysVinit 和 Upstart 初始化系统相关。较早期的 Linux 发行版&#xff08;如早期的 Ubuntu、Red Hat 等&#xff09;使用了这些初始化系统。service 命令用于启动、停止、重启和查询系统服务的状态。虽然许多现代 Linux 发行…

【Python目标识别】Labelimg标记深度学习(YOLO)样本

人工智能、ai、深度学习已经火了很长一段时间了&#xff0c;但是还有很多小伙伴没有接触到这个行业&#xff0c;但大家应该多多少少听过&#xff0c;网上有些兼职就是拿电脑拉拉框、数据标注啥的&#xff0c;其实这就是在标记样本&#xff0c;供计算机去学习。所以今天跟大家分…

(学习笔记-内存管理)如何避免预读失效和缓存污染的问题?

传统的LRU算法存在这两个问题&#xff1a; 预读失效 导致的缓存命中率下降缓存污染 导致的缓存命中率下降 Redis的缓存淘汰算法是通过实现LFU算法来避免 [缓存污染] 而导致缓存命中率下降的问题&#xff08;redis 没有预读机制&#xff09; Mysql 和 Linux操作系统是通过改进…

MPLS 虚拟专用网--跨域OptionA方案

域间概述 随着MPLS VPN解决方案的广泛应用,服务的终端用户的规格和范围也在增长,在一个企业内部的站点数目越来越大,某个地理位置与另外一个服务提供商相连的需求变得非常的普遍,例如国内运营商的不同城域网之间,或相互协作的运营商的骨干网之间都存在着跨越不同自治域的…

在 Amazon DocumentDB 里处理 Decimal128类型数据的解决方案

一道简单的数学题 在开始今天的内容之前&#xff0c;我们先计算一道简单的数学题。0.1 X 0.2 &#xff1f;我相信很多人都笑了&#xff0c;0.02&#xff0c;这是一个孩童都可以回答得出的答案。我们用这道数学题问一下计算机&#xff0c;看看结果又是怎样。 亚马逊云科技开发…

关于我对刚开始学Java的小白想分享的内容:

编程是很有魅力的&#xff0c;让很多人为之痴迷 如果你是初学者&#xff0c;俗称小白&#xff0c;不妨看看下述内容&#xff1a; 文章目录 1. Java 简介1.1 特性介绍1.简单性2. 面向对象3. 分布式4. 健壮性5. 安全性6. 体系结构中立7. 可移植性8. 解释型9. 高性能10. 多线程11…

【javascript】关于path-package

背景 一个老的vue项目&#xff0c;预览pdf文件的时候&#xff0c;电子签章不显示 解决方案 由于是老项目&#xff0c;升级版本存在风险&#xff0c;然后又找到一些解决方案&#xff0c;都是修改源码&#xff0c;修改源码就引出了今天的主题 path-package&#xff0c;我们需要…

maven项目、springboot项目复制文件进来后没反应、不编译解决方法

问题如下 把文件复制进springboot项目后&#xff0c;没反应&#xff0c;不编译。 解决 在maven工具框中选择compile工具&#xff0c;运行即可。

cicd实验

系列文章目录 文章目录 系列文章目录一、1.2. 二、安装并使用1.安装gitlab2.//Jenkins安装3. 总结 一、 1. 2. 二、安装并使用 需要三台服务器一台安装gitlab 192.168.169.10 第二台负责 安装jenkins 192.168.169.20 第三台是负责业务 192.168.169.30 1.安装gitlab yum in…

JVM总结笔记

JVM JVM是什么?JVM 的主要组成部分JVM工作流程JVM内存模型直接内存与堆内存的区别&#xff1a;堆栈的区别Java会存在内存泄漏吗&#xff1f;简述Java垃圾回收机制垃圾收集算法轻GC(Minor GC)和重GC(Full GC)新生代gc流程JVM优化与JVM调优 JVM是什么? JVM是Java Virtual Mach…

【自用记录】常见的第三方接口加密签名方式(ASCll码字典序、URL键值对、 SHA-256加密、MD5加密)

案例1: 案例2: 以上第三方接口都用类似的加密签名方式,两者有类似的部分: 方案1的: $kdata = array(parkId=>$parkId,ts => $ts,serviceCode=>getParkingPaymentList,reqId => $reqId,plateNo => $car_code,//车牌 可为空pageIndex => 1,//第几页page…

15. Spring AOP 的实现原理 代理模式

目录 1. 代理模式 2. 静态代理 3. 动态代理 3.1 JDK 动态代理 3.2 CGLIB 动态代理 4. JDK 动态代理和 CGLIB 动态代理对比 5. Spring代理选择 6. Spring AOP 实现原理 6.1 织入 7. JDK 动态代理实现 8. CGLIB 动态代理实现 9. 总结 1. 代理模式 代理模式&#xf…

<Git/Gerrit>版本控制Git以及代码评审Gerrit常见的开发操作

下载安装,环境变量配置直接百度; 1.代码拉取: 操作步骤&#xff1a;在正确配置完git的条件下:在本地文件夹下&#xff1a;右键–Git Bash -Here&#xff1a; 出现如下弹窗: 在黑窗口输入:代码拉取路径(一般都是把命令和路径在外面写好,直接粘贴(在窗口右键,Paste,回车)) 代码…

linux系统磁盘性能监视工具iostat

目录 一、iostat介绍 二、命令格式 三、命令参数 四、参考命令&#xff1a;iostat -c -x -k -d 1 &#xff08;一&#xff09;输出CPU 属性值 &#xff08;二&#xff09;CPU参数分析 &#xff08;三&#xff09;磁盘每一列的含义 &#xff08;四&#xff09;磁盘参数分…

AI生成式视频技术来临:Runway Gen-2文本生成视频

Runway Gen-2的官方网站提供了一种文本生成视频的工具。以下是对该工具的介绍&#xff1a; 文本生成视频&#xff1a;Runway Gen-2是一个创新的在线工具&#xff0c;可以将文本转化为视频。用户只需输入文本描述或句子&#xff0c;Runway Gen-2就能自动生成相应的视频内容。这…

uni-ajax网络请求库使用

uni-ajax网络请求库使用 uni-ajax是什么 uni-ajax是基于 Promise 的轻量级 uni-app 网络请求库,具有开箱即用、轻量高效、灵活开发 特点。 下面是安装和使用教程 安装该请求库到项目中 npm install uni-ajax编辑工具类request.js // ajax.js// 引入 uni-ajax 模块 import ajax…

【最短路算法】SPFA

引入 在计算机科学的世界里&#xff0c;算法就像是星空中的繁星&#xff0c;各自闪烁着智慧的光芒。它们沉默而坚定&#xff0c;像是一群不语的哲人&#xff0c;默默地解答着世界的问题。 算法的步骤&#xff0c;如同优美的诗行&#xff0c;让复杂的问题在流转的字符中得以释…

PHP8的常量-PHP8知识详解

常量和变量是构成PHP程序的基础&#xff0c;在PHP8中常量的这一节中&#xff0c;主要讲到了定义常量和预定义常量两大知识点。 一、定义常量 定义常量也叫声明常量。在PHP8中&#xff0c;常量就是一个标识符&#xff08;名字&#xff09;&#xff0c;一旦定义&#xff08;声明&…

ansible-playbook编写 lnmp 剧本

ansible-playbook编写 lnmp 剧本 vim /opt/lnmp/lnmp.yaml执行剧本 ansible-playbook lnmp.yaml

WebDAV之π-Disk派盘 + DEVONthink

DEVONthink是由一家来自德国的老牌软件开发商发布的「知识管理」软件,运行于 Mac/iOS 平台。官方自己定位为全方位(中文环境下略有遗憾)帮助你实现知识管理,可以称之为“模块级”应用了。 DEVONthink还支持各种云服务同步,文件管理您的终极文件管理应用、文件、图片与连接远…