[超全汇总]数据结构与算法(4)单调栈,单调队列图文技巧详解系列

单调栈系列

其中nums[i] > nums[st.peek()]是我们的核心,代表找到咯;实际上我们都是维护队列或栈中有一个最大值,无论是单调递增也好,还是单调递减也好

  • 具体可以看看如下几题,大家就明白了

739每日温度

在这里插入图片描述

最简单的就是暴力解法,记录nums[j]>nums[i]的天数是j-i

//暴力解法时间复杂度On² 空间ON
class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int[]res = new int[temperatures.length];
        for(int i  = 0;i<temperatures.length-1;i++){
            for(int j = i+1;j<temperatures.length;j++){
                if(temperatures[j]>temperatures[i]){
                    res[i] = j-i;
                    break;
                }
            }
        }
        return res;
    }
}
//使用单调栈降低到On
class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int n = temperatures.length;
        int[]res = new int[n];
        //存放元素索引
        Stack<Integer> s = new Stack<>();
        for(int i = n-1;i>=0;i--){
            while(!s.isEmpty() && temperatures[s.peek()]<=temperatures[i]){
                //小数出栈
                s.pop();
            }
            res[i] = s.isEmpty()? 0:(s.peek()-i);
            s.push(i);
        }
        return res;
    }
}

496.下一个更大的元素

在这里插入图片描述

  • 这一题和温度有点像但又不完全像,这里有两个数组,其中我们要比较的是nums1的数在nums2中有没有下一个更大的数,实际上就是要操作Nums2;

    • 这里我们需要借助HashMap集合来存储,因为没有重复元素,所以我们将Nums1遍历后,记录其下表与数值,这样根据遍历Nums2的时候,找到合适的数了我们就可以找到对应的Index;
    • 比如说,我们遍历nums2, 将nums2的数压进去,我们将nums2[0]压进去,我们的for从1开始
    • nums[i] <= nums[stack.peek()],那么我们就可以加它压入栈中,此时也就不满足题意
    • 如果Nums2[i]>栈顶元素,说明此时右边的大一些,这个时候判断map.cntainsKey(stack.peek())是否在1中,如果在的话,取出其索引值,对该索引进行赋值即可。
class Solution {
    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        Stack<Integer> temp= new Stack<>();
        int[]res  = new int[nums1.length];
        //进行初始化,后面只需要对符合的进行改变
        Arrays.fill(res,-1);
        HashMap<Integer,Integer>map = new HashMap<>();
        for(int i =0;i<nums1.length;i++){
            map.put(nums1[i],i);
        }
        temp.push(0);//将Nums1先压进去
        for(int i =1;i<nums2.length;i++){
            if(nums2[i]<=nums2[temp.peek()]){
                temp.push(i);
            }else{
                //代表找到下一个更大的元素了
                while(!temp.isEmpty() && nums2[temp.peek()]<nums2[i]){
                    if(map.containsKey(nums2[temp.peek()])){
                        Integer index = map.get(nums2[temp.peek()]);
                        res[index] = nums2[i];
                    }
                    temp.pop();
                }
                //将nums[i]压入栈中继续进行比较
                temp.push(i);
            }
        }
        return res;
    }
}

503:下一个更大的元素II

在这里插入图片描述

//和温度不一样的在于这里我们是一个循环数组,当设计到循环的时候就要有点循环队列的意识,对长度取模
//索引我们是index = i % size
class solution{
    public int[]nextGreaterElements(int[]nums){
        //进行边界的判断
        if(nums==null || nums.length<=1){
            return new int[]{-1};
        }
        int size = nums.length;
        int [] result = new int[size];
        //初始化数组-1
        Arrays.fill(res,-1);
        Stack<Integer>st = new Stack<>();
        for(int i = 0;i<2 * size;i++){
            //只要栈不为空,或者此时nums[i]>nums[st.peek()]就可加入res
            while(!st.isEmpty() && nums[i%size] > nums[st.peek()]){
                result[st.peek()] = nums[i%size];
                st.pop();
            }
            st.push(i%size);
        }
        return result;
    }
}
//法二
class Solution {
    public int[] nextGreaterElements(int[] nums) {
        int n = nums.length;
        int[]res = new int[n];
        Stack<Integer>stack = new Stack<>();
        for(int i = 2*n-1;i>=0;i--){
            while(!stack.isEmpty() && stack.peek()<=nums[i%n]){
                stack.pop();
            }
            res[i % n] = stack.isEmpty()? -1: stack.peek();
            stack.push(nums[i%n]);
        }
        return res;
    }
}

42.接雨水

在这里插入图片描述

//单调栈,构造一个单调下减的栈【5,3,1
//雨水面积主要是根据我们弹出的最小的值减去凹槽的值
//雨水的高度是h = Math.min(height[st.peek()],height[i])-height[st.pop()];
//雨水的宽度是:i-st.peek()-1
class Solution {
    public int trap(int[] height) {
        int n = height.length;
        int result = 0;
        int curIndex = 0;
        Deque<Integer>stack = new Deque<>();
        while(curIndex<n){
            while(!stack.isEmpty() && height[curIndex] > height[stack.peek()]){
                //弹出凹槽值
                int top = stack.pop();
                if(stack.isEmpty()) break;
                int h = Math.min(height[stack.pop()],height[curIndex])-height[top];
                int dist = curIndex-stack.peek()-1;
                result += dist * h;
            }
            stack.push(curIndex++);
        }
        return result;
    }
}
class Solution {
    public int trap(int[] height) {
        int ans = 0;
        Deque<Integer> stack = new ArrayDeque<>();
        int n = height.length;
        int i = 0;
        // for (int i = 0; i < n; ++i) {
            while(i<n){
            // height[i] > height[stack.peek()]得到一个可以接雨水的区域
            while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
                int top = stack.pop();  // 获得接雨水位置的下标
                if (stack.isEmpty()) {  // 栈为空,左边不存在最大值,无法接雨水
                    break;
                }
                int left = stack.peek(); // left成为新的栈顶元素
                int currWidth = i - left - 1;  // 获取接雨水区域的宽度
                int currHeight = Math.min(height[left], height[i]) - height[top];
                ans += currWidth * currHeight;
            }
            stack.push(i++); // 在对下标i处计算能接的雨水量之后,将i入栈,继续遍历后面的下标
        }
        return ans;
    }
}

84.柱状图中的最大的矩型

在这里插入图片描述
在这里插入图片描述

//创建一个单调递增栈
//面积等于右边界,也就是stack.pop() * 宽度:i - stack.peek() -1,其中左边界就是stack.peek();
class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        //定义一个全局最大值
        int max = 0;
        Deque<Integer>stack = new ArrayDeque<>();
        //出始栈底为-1
        stack.push(-1);
        for(int i = 0;i<n;i++){
            while(stack.peek()!=-1 && heights[stack.peek()]>=heights[i]){
                max = Math.max(max,heights[stack.pop()]*(i-stack.peek()-1));
            }
            stack.push(i);
        }
        while(stack.peek()!=-1){
            max = Math.max(max,heights[stack.pop()]*(n-stack.peek()-1));
        }
        return max;
    }
}

11.呈最多雨水的容器

在这里插入图片描述

这里采用双指针,对比接雨水这题相对来说是简单的,因为我们无非就是算面积最大值,而面积是最小值乘以两者的长度

class solution{
      public int maxArea(int[] height) {
          int left = 0;
          int right = height.length-1;
          int res = 0;
          while(left < right){
              //记录当前的高度值大小
              int cur_val = Math.min(height[left],height[right]) * (right-left);
              res = Math.max(cur_val,res);
              if(height[left] < height[right]){
                  left++;
              }else{
                  right--;
              }
          }
          return res;
      }
}

239滑动窗口最大值

在这里插入图片描述

  • 观察滑动窗口的过程可以发现,实现单调队列必须使用支持在头部和尾部进行插入和删除的,很明显双链表是满足该条件的
  • 单调队列和单调栈的核心思路没有变化,Push依旧是在队尾添加,但是要把前面比自己小的都删掉,保证加进来的是队列中最小的一个也就是[5 4 3 ] 如果6加进来就需要把前面的移除,但是如果是1呢,就可以直接加进去;维持一个单调递减的队列

在这里插入图片描述

  • max方法就是获取队列首的元素
public int max(){
    return q.getFirst();
}
  • pop方法在队头删除元素n
public void pop(int n){
    if(n == q.getFirst()){//因为第一个可能已经不复存在了
        q.pollFirst();
    }
}
  • 有个小细节,linkedList支持在头部和尾部快速增删元素,而ArrayList结构在后序的取元素更适合
//方法1
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        //创建一个单调队列
        int n= nums.length;
        int[]res = new int[n-k+1];
        Deque<Integer>deque =new ArrayDeque<>();
        int index = 0;
        for(int i = 0;i<k;i++){
            //按升序进行排列
            while(!deque.isEmpty() && deque.peekLast()<nums[i]){
                deque.removeLast();
            }
                deque.offerLast(nums[i]);
            
        }
        res[index++]=deque.peekFirst();
        for(int i = k;i<nums.length;i++){
            if(nums[i-k]==deque.peekFirst()){
                deque.removeFirst();
            }
            while(!deque.isEmpty()&&deque.peekLast()<nums[i]){
                deque.removeLast();
            }
            deque.offerLast(nums[i]);
            res[index++]=deque.peekFirst();
        }
        return res;
    }
}
//方法进行封装
class Solution {
    class MonotonicQueue{
        LinkedList<Integer>q = new LinkedList<>();
        public void push(int n){
            while(!q.isEmpty() && q.getLast() < n){
                q.pollLast();
            }
            q.addLast(n);
        }
        public void pop(int n){
            if(n == q.getFirst()){
                q.pollFirst();
            }
        }
        public int max(){
            return q.getFirst();
        }
    }
    public int[] maxSlidingWindow(int[] nums, int k) {
        MonotonicQueue window = new MonotonicQueue();
        List<Integer>res = new ArrayList<>();
        for(int i = 0;i<nums.length;i++){
            //将前几个先装进去
            if(i < k - 1){
                window.push(nums[i]);
            }else{
                window.push(nums[i]);
                res.add(window.max());
                //移除旧数字
                window.pop(nums[i-k+1]);
            }
        }
        int[]arr = new int[res.size()];
        for(int i = 0;i<res.size();i++){
            arr[i] = res.get(i);
        }
        return arr;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李知恩真爱粉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值