Leetcode - Largest Rectangle in Histogram

https://oj.leetcode.com/problems/largest-rectangle-in-histogram/

有图,描述自己进链接看。。。


 public int largestRectangleArea(int[] height)

这题可以暴力做,也就是二重循环即可。但是这样就失去本题的意义了

所以,我们要考虑一个更巧妙的O(N)的做法。这就要利用到Stack。我先直接给出算法,随后给出代码

算法如下,循环往下走,当符合以下两个条件的时候,压栈:1.栈为空。2.栈顶比当前元素小。压栈的内容是当前的index。

当遇到栈顶比当前元素大的时候,做以下循环,不停pop栈,并且求与前一个bar之间所产生的面积,维护其中最大者。

另外当循环完结之后,若堆栈内还有元素,依旧可以尝试不停pop然后维护面积

    public int largestRectangleArea(int[] height) {
        Stack<Integer> index_stack = new Stack<Integer>();
        int sum = 0;
        for(int i = 0; i < height.length; i++){
            if(index_stack.isEmpty() || height[i] >= height[index_stack.peek()])
                index_stack.push(i);
            else{
                while(!index_stack.isEmpty() && height[index_stack.peek()] > height[i]){
                    int index = index_stack.pop();
                    sum = Math.max(sum, index_stack.isEmpty() ? height[index] * i : height[index] * (i - index_stack.peek() - 1));
                } 
                index_stack.push(i);
            }
        }
        while(!index_stack.isEmpty()){
            int index = index_stack.pop();
            sum = Math.max(sum, index_stack.isEmpty() ? height[index] * height.length : height[index] * (height.length - index_stack.peek() - 1));
        }
        return sum;
    }

2017-12-12 Updated:

重新回顾这题目的时候,我会发现我自己最不熟悉的题目就是只能死记硬背并无法理解题目算法的题目。这就是其中一题。所以我花了一点时间去理解这一题为什么是要这么做的。那么,我先给出一个比上面那个做法次一些的brute force。

算法的概念是这样的,因为在这个图里面,给定左边界和右边界。正方形的面积是等于左边界和右边界的距离乘以之间最短的那根bar的长度。所以如果我们给定一根bar为中心,找到以这个bar为中心的最左边界和最右边界(边界的定义是不小于当前bar高度的眼神边界,因为一旦超过了边界,出现了比当前bar矮的bar,那么这个bar作为高度的界定点就失效了。)计算面积,我们遍历图里面的所有bar,做这样的边界界定和面积计算,我们总是能够找到最大的面积的。不需要担心在计算较高的bar时因为边界的界定错过了更矮高度的bar而错过面积的计算,因为每一根bar都会有成为一次中心的机会,当更矮的bar做为中心时,它的边界计算总是会覆盖更高的bar。所以这样是可以取到最大的面积的。这种算法的复杂度是o(n^2)。因为每一根bar为中心时,我们都会可能往左往右遍历一个o(n)。所以n根bar的计算就会产生o(n^2)的效率。

下面给出代码:

    public int largestRectangleArea(int[] heights) {
        int result = 0;
        for (int i = 0; i < heights.length; i++) {
            result = Math.max(result, bruteForce(heights, i));
        }
        
        return result;
    }
    
    private int bruteForce(int[] height, int mid) {
        int left = mid, right = mid;
        while (left >= 0 && height[left] >= height[mid]) left--;
        while (right < height.length && height[right] >= height[mid]) right++;
        return height[mid] * (right - left - 1);
    }

这个代码是过不了leetcode的,因为会有一个非常极端的case出现TLE而无法通过。所以最后的通过率是95/96。leetcode就是那么贱,总会给你一个或者几个极端的case去挑战你的performance下限。

但是最优的算法就是在这个brute force上进化而来。

所以最优的算法实际上就是用o(n)的办法找到每一个midbar所对应的左边界和右边界。换一句话说,就是找到midbar左边第一个比midbar小的数,和右边第一个比midbar小的数。那么回到最优的算法,下面是分析。先贴一段更容易理解的代码:

    public int largestRectangleArea(int[] heights) {
        Stack<Integer> cacheStack = new Stack<Integer>();
        int result = 0;
        for (int i = 0; i < heights.length; i++) {
            if (cacheStack.isEmpty() || heights[cacheStack.peek()] < heights[i]) {
                cacheStack.push(i);
            } else {
                while(!cacheStack.isEmpty() && heights[cacheStack.peek()] > heights[i]) {
                    int midBar = cacheStack.pop();
                    int leftBoundary = cacheStack.isEmpty() ? 0 : cacheStack.peek() + 1;
                    result = Math.max(result, heights[midBar] * (i - leftBoundary));
                }
                
                cacheStack.push(i);
            }
        }        
        
        while(!cacheStack.isEmpty()) {
            int midBar = cacheStack.pop();
            int leftBoundary = cacheStack.isEmpty() ? 0 : cacheStack.peek() + 1;
            result = Math.max(result, heights[midBar] * (heights.length - leftBoundary));
        }
        
        return result;
    }


先说关键几点。

1. 事实上Stack里面的数字所对应的高度是排好序的。

2. Stack上面的下面的一个元素实际上就是上面的一个元素的左边界。如果这个元素是栈底,表示heights里左边并没有比它小的,0就是它的左边界。如果循环结束Stack里面

还有残留的元素,栈顶必然是最后一个元素,同时也表示,栈中元素的右方,并没有比它小的元素。所以栈中元素的右边界是这个histogram数组的长度。

3. 在else的逻辑里,我们每pop出来一个元素,实际上pop出来的是brute force里每一轮里的midBar,当前的i是右边界。根据2我们可以得知peek()是左边界。

4. 因为每一个元素都会被push一次。根据3,我们可以得知我们已经用了O(N)的方式做到了上述暴力解所做的事情。遍历所有midbar,找到符合条件的左右边界,求出所有midbar对应的正方形中的最大解。


结合算法我们可以得到一定的解析: 

1. 算法里,如果当前高度等于栈顶index对应的高度,我们就push,这个做到了上面关键点1和2。

2. 然后如果当前高度小于栈顶index高度,就表示当前所在高度就是栈顶上面所有大于当前栈顶index高度的元素集的右边界。所以此时我们知道了右边界和左边界。我们就把栈顶符合条件的元素都push出来计算一次。

3. 根据1和2,可以保证2的后半句也可以达成。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目标检测(Object Detection)是计算机视觉领域的一个核心问题,其主要任务是找出图像中所有感兴趣的目标(物体),并确定它们的类别和位置。以下是对目标检测的详细阐述: 一、基本概念 目标检测的任务是解决“在哪里?是什么?”的问题,即定位出图像中目标的位置并识别出目标的类别。由于各类物体具有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具挑战性的任务之一。 二、核心问题 目标检测涉及以下几个核心问题: 分类问题:判断图像中的目标属于哪个类别。 定位问题:确定目标在图像中的具体位置。 大小问题:目标可能具有不同的大小。 形状问题:目标可能具有不同的形状。 三、算法分类 基于深度学习的目标检测算法主要分为两大类: Two-stage算法:先进行区域生成(Region Proposal),生成有可能包含待检物体的预选框(Region Proposal),再通过卷积神经网络进行样本分类。常见的Two-stage算法包括R-CNN、Fast R-CNN、Faster R-CNN等。 One-stage算法:不用生成区域提议,直接在网络中提取特征来预测物体分类和位置。常见的One-stage算法包括YOLO系列(YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5等)、SSD和RetinaNet等。 四、算法原理 以YOLO系列为例,YOLO将目标检测视为回归问题,将输入图像一次性划分为多个区域,直接在输出层预测边界框和类别概率。YOLO采用卷积网络来提取特征,使用全连接层来得到预测值。其网络结构通常包含多个卷积层和全连接层,通过卷积层提取图像特征,通过全连接层输出预测结果。 五、应用领域 目标检测技术已经广泛应用于各个领域,为人们的生活带来了极大的便利。以下是一些主要的应用领域: 安全监控:在商场、银行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值