常用算法汇总

回溯法

回溯法教程

动态规划

动态规划教程

滑动窗口

滑动窗口教程

贪心算法

贪心算法教程

排列组合

排列组合教程

单调栈

以例子来分析

例1
题目:给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。(Leetcode84题)

解题思路:我们可以遍历每根柱子,以当前柱子 i 的高度作为矩形的高,那么矩形的宽度边界即为向左找到第一个高度小于当前柱体 i 的柱体,向右找到第一个高度小于当前柱体 i 的柱体。对于每个柱子我们都如上计算一遍以当前柱子作为高的矩形面积,最终比较出最大的矩形面积即可。

暴力法

class Solution {
    //由中心向两侧扩散
    public int largestRectangleArea(int[] heights) {
        if(heights.length==0){
            return 0;
        }
        if(heights.length==1){
            return heights[0];
        }
        int max=0;
        for(int i=0;i<heights.length;i++){
            int left=1;
            int right=1;
            while(i-left>=0&&heights[i-left]>=heights[i]){
                left++;
            }
            while(i+right<heights.length&&heights[i+right]>=heights[i]){
                right++;
            }
            if((right+left-1)*heights[i]>max){
                max=(right+left-1)*heights[i];
            }
        }
        return max;

    }
}

运行结果:用时1350ms,内存消耗41.6MB

单调栈

解题思路
以上暴力写法 Java 可以通过,但我们不妨想一下这里的双重循环是否可以优化?

我们每遍历到当前柱体 i 时:

  • 上述写法中,我们需要再嵌套一层 while 循环来向左和向右找到第一个比柱体 i 高度小的柱体,这个过程是 O(N)的;
  • 那么我们可以 O(1)的时间获取柱体 i 左边第一个比它小的柱体吗?答案就是单调增栈,因为对于栈中的柱体来说,栈中下一个柱体就是左边第一个高度小于自身的柱体。

解法
因此做法就很简单了,我们遍历每个柱体,若当前的柱体高度大于等于栈顶柱体的高度,就直接将当前柱体入栈,否则若当前的柱体高度小于栈顶柱体的高度,说明当前栈顶柱体找到了右边的第一个小于自身的柱体,那么就可以将栈顶柱体出栈来计算以其为高的矩形的面积了

class Solution {
    //改进中心扩散法,使用单调栈对中心扩散法进行优化
    //使用中心扩散法时,外层for循环,内层需要两个while循环,可以使用单调栈把内层时间优化为O(1)
    public int largestRectangleArea(int[] heights) {
        if(heights.length==0){
            return 0;
        }
        //在原数组的头和尾部分别加一个元素0
        // 这里为了代码简便,在柱体数组的头和尾加了两个高度为 0 的柱体。
        int[] arr=new int[heights.length+2];
        //将指定源数组中的数组从指定位置复制到目标数组的指定位置
        System.arraycopy(heights,0,arr,1,heights.length);
        //ArrayDeque是Deque接口的一个实现,使用了可变数组,所以没有容量上的限制
        //ArrayDeque是线程不安全的,在没有外部同步的情况下,不能再多线程环境下使用
        //ArrayDeque是Deque的实现类,可以作为栈来使用,效率高于Stack
        Deque<Integer> stack=new ArrayDeque<>();//栈中存储的是数组的索引
        int max=0;
        for(int i=0;i<arr.length;i++){
            // 对栈中柱体来说,栈中的下一个柱体就是其「左边第一个小于自身的柱体」;
            // 若当前柱体 i 的高度小于栈顶柱体的高度,说明 i 是栈顶柱体的「右边第一个小于栈顶柱体的柱体」。
            // 因此以栈顶柱体为高的矩形的左右宽度边界就确定了,可以计算面积\U0001f336️ ~
            while(!stack.isEmpty()&&arr[i]<arr[stack.peek()]){
                int h=arr[stack.pop()];
                max=Math.max(max,(i-stack.peek()-1)*h);
            }
            stack.push(i);
        }
        return max;
    }
}

运行结果:运行时间13ms,内存消耗40.7MB

例2

题目:给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。(LeetCode42)

解题思路简单来说就是当前柱子如果小于等于栈顶元素,说明形不成凹槽,则将当前柱子入栈;反之若当前柱子大于栈顶元素,说明形成了凹槽,于是将栈中小于当前柱子的元素pop出来,将凹槽的大小累加到结果中

代码

class Solution {
    //单调栈, 该题和最大矩形使用单调栈的方法不一样,求最大矩形需要把小于栈顶的元素入栈,这里需要把大于栈顶的元素入栈
    //接雨水需要形成凹槽,所以栈里面的元素都是递减的,如果当前元素大于栈顶的元素,则就形成凹槽
    //依次遍历每个元素,按照上述方法,求出最大值
    public int trap(int[] height) {
        if(height.length<3){
            return 0;
        }
        //存储数组的索引
        Deque<Integer> stack=new ArrayDeque<>();
        int sum=0;
        for(int i=0;i<height.length;i++){
            while(!stack.isEmpty()&&height[i]>height[stack.peek()]){
                //当前凹槽的底部
                int mid=height[stack.pop()];
                //如果栈顶下一个元素和栈顶元素相等则全部弹出栈
                while(!stack.isEmpty()&&height[stack.peek()]==mid){
                    stack.pop();
                }
                // stack.peek()是此次接住的雨水的左边界的位置,右边界是当前的柱体,即i。
                // Math.min(height[stack.peek()], height[i]) 是左右柱子高度的min,减去mid就是雨水的高度。
                // i - stack.peek() - 1 是雨水的宽度。
                if(!stack.isEmpty()){
                    sum+=((Math.min(height[i],height[stack.peek()])-mid)*(i-stack.peek()-1));
                }
            }
            stack.push(i);
        }
        return sum;

    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值