学会单调栈,一键玩转力扣困难题!

本文详细介绍了单调栈的基本概念、结构以及使用模板,通过一个简单的LeetCode问题阐述了单调栈解决Next Greater Element问题的过程,并展示了如何用单调栈解决更复杂的元素值筛选问题。总结了单调栈的核心操作:for循环内嵌while弹出不需要的元素,再进行栈的判断与入栈操作。
摘要由CSDN通过智能技术生成

单调栈

作为算法中最基础且经典的一个先进后出的数据结构,栈往往是很多题目的解题关键。


聊一聊栈

一、栈的结构

栈是一种典型的(FILO, First in last out)结构,好比往井里丢的石头一样,最先入栈的数据沉底,最后入栈的数据在最上方。

这里只需要知道栈的使用方法即可,栈的实现非常的简单,可以用一个数组和一个index索引取模拟实现.

代码如下(示例):

而实际上,用List实现栈是更方便的。

比如在C++中#include <stack>

java中的Deque (Stack被废弃后建议使用deque,效率更高更安全)

public class MyStack<T>{
    // 用于储存元素
    private T[] element;
    // 永远指向最后加入的元素
    private int index;

    void push(T object){
        if(index==element.length){
            throw new Exception("stack is full");
            return;
        }
        element[index] = object;
        index++;
    }

    T pop(){
        if(element.length==0){
            return null;
        }
        int res = element[index];
        index--;
        return res;
    }

    T peek(){
        return element[index];
    }
}

二、栈的使用模板

1.引入模版

我们首先来看一到leetcode的简单题,使用单调栈来进行解答.

下一个更大元素I : next-greater-element-i

题目:

给两个数组num1,num2. 其中num1是num2的子集,对每一个num1的元素找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素,作为查询答案放回答案数组中,如果没有更大元素返回-1

nums1 = [4,1,2], nums2 = [1,3,4,2].

res = [-1,3,-1].

对数字1, 找到nums2 = [1,3,4,2]。下一个更大元素是 3 ,其他两个数字没有更大元素.

解题思路与代码:

1. 我们已经知道num1是num2的子集,那么所有的num1元素都可以在num2中找到对应

2. 那么我们只需要知道num2中每一个元素i的“下一个更大值j,

    装在一个Map字典中. key: i, value : j

3. 对数组1的每一个值找到它的字典对应值,那么就得到了答案.

// 所以重点就是,我们如何找到num2中的下一个更大值?

public Map<Integer,Integer> nextGreaterForArray(int[] num2){
    /** 首先从边界开始思考:最右边的元素已经没有下一个值了,它的对应value必定为-1
     于是我们开始从右往左循环
    */
    Deque<Integer> stack = new LinkedList<>();
    Map<Integer,Integer> map = new HashMap<>();
    // 我们每一次都把当前元素入栈,下一次循环时,把当前元素和上一个栈内的元素对比
    for(int i = num2.length-1; i>=0; i--){
        // 如果我当前元素比栈内元素大,则把不需要的“矮个子”弹出
        while(!stack.isEmpty() && num2[i]>=stack.peek()){
            stakc.pop();
        }
        // 如果一直弹出到没有元素了,说明当前元素就是最大值,higher=-1,否则就为栈顶元素
        int higher = stack.isEmpty()? -1:stack.peek();
        // 记录与入栈
        map.put(num2[i],higher);
        stack.push(num2[i]);
    }
    return map;
}

可以看到单调栈最主要的模板是for循环里面嵌套了一个while循环,用于弹出我们不要的值。然后对栈进行空值判定和入栈,当这个Map字典拿到后,这道题也就迎刃而解了。

2.困难题

2334元素值大于变化阈值的子数组

题设:

给你一个整数数组 nums 和一个整数 threshold 。

找到长度为 k 的 nums 子数组,满足数组中 每个 元素都 大于 threshold / k 。

请你返回满足要求的 任意 子数组的 大小 。如果没有这样的子数组,返回 -1 。

子数组 是数组中一段连续非空的元素序列。

示例 1:

输入:nums = [1,3,4,3,1], threshold = 6
输出:3
解释:子数组 [3,4,3] 大小为 3 ,每个元素都大于 6 / 3 = 2 。
注意这是唯一合法的子数组。

解题思路和代码:

困难题一般是多个套路一起用才能解题。

第一眼从竞赛上看到这个题的时候愿意为是DFS+记忆化搜索

后来才发现是贪心+单调栈

贪心:一段区间内每个元素都大于threshould/k,在循环遍历时,如果假设我们当前取的元素就为区间的最小值,往两边去查找边界。那么区间长度越长,k越大,threshould/k则越小

如何去查询边界?单调栈.

(这不就成了去查找下一个更小元素和上一个更小元素吗)

public int validSubarraySize(int[] nums, int threshold) {
        Deque<Integer> stack = new LinkedList<>();
        int n = nums.length;
        // left,right数组记录每个元素的左区间和右区间
        int[] left = new int[n];
        int[] right = new int[n];
        Arrays.fill(left,-1);
        Arrays.fill(right,n);
        // 查找每个元素左边更小值,模板来了! for+while弹出不要的元素(更大的)+判空记录+入栈
        for(int i=0;i<n;i++){
            while(!stack.isEmpty() && nums[i]<=nums[stack.peek()]){
                stack.pop();
            }
            if(!stack.isEmpty()){
                left[i] = stack.peek();
            }
            stack.push(i);
        }

        stack.clear();
        
        // 同理查找右边的元素, for+while+if+stack.push
        for(int i=n-1;i>=0;i--){
            while(!stack.isEmpty() && nums[i]<=nums[stack.peek()]){
                stack.pop();
            }
            if(!stack.isEmpty()){
                right[i] = stack.peek();
            }
            stack.push(i);
        }
        // 对于每一个元素形成的区间来进行最后判定,如果有一个满足了条件,返回它
        for(int i =0;i<n;i++){
            int k = right[i]-left[i]-1;
            if((double)(nums[i]*k)>(double)threshold) return k;
        }
        return -1;
    }

总结

以上就是单调栈的使用模板,For+while(弹出不要的元素!)+if(stack.isEmpty()) +stack.push 。

你学废了吗!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值