前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >算法细节系列(12):破除想当然

算法细节系列(12):破除想当然

作者头像
用户1147447
发布2019-05-26 09:43:00
2880
发布2019-05-26 09:43:00
举报
文章被收录于专栏:机器学习入门

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1434726

破除想当然

总结最近遇到的一些有趣题。知识点主要有【动态规划】,【栈】,【数组】,【状态记录】,这些题目挺有趣,主要原因在于写程序时,需要破除想当然的人类求解过程,而是回归到低级的计算机思维,一步步教计算机怎么做。

题目均摘自leetcode,分为以下三题(求面积系列)。

  • 84 Largest Rectangle in Histogram
  • 85 Maximal Rectangle
  • 221 Maximal Square

84 Largest Rectangle in Histogram

之前刚好总结了博文【next Greater Element系列】,以下内容将用到该博文讲到的知识点,可以先去瞧瞧,方便理解。

Problem:

Given n non-negative integers representing the histogram’s bar height where the width of each bar is 1, find the area of largest rectangle in the histogram.

Above is a histogram where width of each bar is 1, given height = 2,1,5,6,2,3.

The largest rectangle is shown in the shaded area, which has area = 10 unit.

For example, Given heights = 2,1,5,6,2,3, return 10.

暴力做法:针对每个元素,遍历一遍数组,找出最大的宽度,求出面积。即使这样答案也不那么显而易见,这里就不贴出代码了。我们再来看看另一种思路。

先考虑人类是如何思考这问题的,当我看到这图的第一眼,我就扫出了答案,面积不就是10么。可你想过你大脑是怎么工作的么?

两点:状态记录+回溯求解,这些方法在你脑中一闪而过,但在计算机视角中没那么简单,要想理清楚得费一些周折。

简单来说,我们在扫面积的时候,当遍历一个元素后,我们已经把先前状态记录在脑海中,所以在对下一个元素比较时,我们自然有了之前的信息,但观察很多for循环语句你会发现,它们均无状态记录,遍历一遍就过去了,这都是暴力的做法。所以从中就能得到一些优化的细节,如在next Greater Element系列中,提到的栈,就是为了保存之前的路径而存在。

那么该题目的思路是什么?其实再思考一步,答案就出来了,如当我们扫到第二个元素1时,我们怎么求它的面积?不就是当前最小高度乘以宽度2,答案是2么?没错,在你做的过程中,你天然的把第一个元素的信息利用上去了(高度的比较);其次,计算面积时,你把第一个元素的多余部分给切除了。

所以,现在的过程就很简单了,每当遍历到一个新元素时,不管三七二十一,把它放入一个容器中,记录当前状态,当我遍历到下一个元素时,出现两种情况咯,要么比它大,要么比它小。比它小的之前已经说过了,计算面积时,把前一个元素高出的部分切除,计算面积。比它大的咋办呢?暂时放着呗,这点也是人容易忽略的步骤,假想我们不知道数组的长度,但数组是不断递增的,你要如何得到整个数组的面积?你肯定得看完所有数组啊!而且你也知道,只要当它不断递增,那么从刚开始递增的那个元素开始,它一定是最大面积。所以你有必要等到数组开始递减为止,是吧。

好了,说了那么多,再理理思路,代码就能出来了,直接上代码。

代码语言:javascript
复制
public int largestRectangleArea(int[] heights) {

        Stack<Integer> stack = new Stack<>();

        int max = 0;
        //注意 边界条件的处理
        for (int i = 0; i <= heights.length; i++){
            int curr = (i == heights.length) ? 0: heights[i];
            // 注意  count 和 heights[pos] = curr
            int count = 0;
            while(!stack.isEmpty() && heights[stack.peek()] > curr){
                int pos = stack.pop();
                max = Math.max(max, heights[pos] * (i - pos));
                heights[pos] = curr;
                count++;
            }
            i -= count;
            stack.push(i);
        }

        return max; 
    }

这道题用到了stack记录遍历状态。注意的地方比较多,咋说呢,如果就着自己的思路,代码可以五花八门,所以不必纠结太多细节的东西。

  1. 当前元素小于栈顶元素时,把大于当前元素的元素下标给pop出来,为啥咧,因为我们要开始计算这些递增的面积了,每计算一次,就维护一个最大的max。
  2. 啥时候停止咧,当栈的元素小于当前元素时,停止计算,因为此时,你又回到了不断递增的状态,你得继续输入数组元素了。
  3. 那么问题来了,已经被pop的元素咋办呢,刚才说了,切除多于的部分,意思就是让它们和当前元素的高度相等,它不会影响后续计算。
  4. 此时,这些元素得重新加入栈中,所以有了count计数,把i指定到被切除元素的最后一个下标。
  5. 那么,当数组输入完毕后,栈中还有元素咯?且它们都是递增的,没错。所以有了特殊的循环,遍历数组长度+1次,就是为了在数组最后加个边界处理,很巧妙,元素为0时,一定能把栈中所有元素给吐出来。

所以说,人高级啊,想都不用想直接就能出答案,这反而导致了我们在教计算机做题目时成了一个很大的障碍。呵呵,继续吧。

85 Maximal Rectangle

Problem:

Given a 2D binary matrix filled with 0’s and 1’s, find the largest rectangle containing only 1’s and return its area. For example, given the following matrix: 1 0 1 0 0 1 0 1 1 1 1 1 1 1 1 1 0 0 1 0 Return 6.

嗯哼,如果有了前面那道题的铺垫,这道题就不难了。但怪就怪在,如果没有前面那道题,你就很难想到!就因为被它的矩阵吓到了?这些认知障碍得破除啊。

它是一个平面,让你求最大长方形面积,所以呢,想法就是把矩阵一层一层往下切。先切出第一层,我们能计算每个位置的高度,为1 0 1 0 0,然后切第二层2 0 2 1 1,第三层3 1 3 2 2,第四层4 0 0 3 0,这不就是分别求每一层的面积么,数值就是它们的高度,不用解释。

接下来的问题,就是生成这样的矩阵就好了,很简单,一个dp就能搞定。这不能算严格意义上的dp,但题目说是dp,那就dp吧,所以代码如下:

代码语言:javascript
复制
public int maximalRectangle(char[][] matrix) {

        int row = matrix.length;
        if (row == 0)
            return 0;
        int col = matrix[0].length;
        if (col == 0)
            return 0;

        int[][] w = new int[row + 1][col + 1];

        for (int j = 1; j < col + 1; j++) {
            if (matrix[0][j - 1] == '1') {
                w[1][j] = 1; // 宽
            }
        }

        for (int i = 2; i < row + 1; i++) {
            for (int j = 1; j < col + 1; j++) {
                if (matrix[i - 1][j - 1] == '1') {
                    w[i][j] = w[i-1][j] + 1;
                }
            }
        }

        int max = 0;
        for (int i = 1; i < row+1; i++){
            max = Math.max(max,largestRectangleArea(w[i]));
        }

        return max;
    }

    public int largestRectangleArea(int[] heights) {

        Stack<Integer> stack = new Stack<>();

        int max = 0;
        for (int i = 0; i <= heights.length; i++) {
            int curr = (i == heights.length) ? 0 : heights[i];
            int count = 0;
            while (!stack.isEmpty() && heights[stack.peek()] > curr) {
                int pos = stack.pop();
                max = Math.max(max, heights[pos] * (i - pos));
                heights[pos] = curr;
                count++;
            }
            i -= count;
            stack.push(i);
        }

        return max;
    }

思路很清楚,没什么好解释的,继续下一题。

221 Maximal Square

Problem:

Given a 2D binary matrix filled with 0’s and 1’s, find the largest square containing only 1’s and return its area. For example, given the following matrix: 1 0 1 0 0 1 0 1 1 1 1 1 1 1 1 1 0 0 1 0 Return 4.

呵呵,如果就着前面的思路,那么就完蛋了,反正我是又做不出来了。这道题的思路比较奇葩,是一道较难的dp题。咋说呢,我其实还没掌握它的核心思想。用的是正方形生成性质,很难理解它为啥是正确的。

状态转移方程:

dp[i+1][j+1] = Math.min(dp[i][j+1], Math.min(dp[i+1][j], dp[i][j])) + 1;

其中 i和j,分别表示在当前坐标(i,j)能够生成最大矩形的长。

所以说新的正方形,一定是那三个状态的最小值,否则不可能构成一个更大的正方形,自己笔画下。

代码语言:javascript
复制
public int maximalSquare(char[][] matrix) {
        if (matrix.length == 0 || matrix[0].length == 0) return 0;

        int n = matrix.length, m = matrix[0].length;

        int[][] dp = new int[n+1][m+1];

        int max = 0;
        for(int i = 0; i < n; i++){
            for (int j = 0; j < m; j++){
                if (matrix[i][j] == '1'){
                    dp[i+1][j+1] = Math.min(dp[i][j+1], Math.min(dp[i+1][j], dp[i][j])) + 1;
                    max = Math.max(max, dp[i+1][j+1]);
                }
            }
        }


        return max * max;
    }

这道题给了我dp的一个新思路,不一定dp要记录每一步的最优解,即dp到最后不一定就是本题的答案,相反,我们可以在dp更新的时候,时刻更新max,那么求解它的思路和想法就广了很多。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017年04月26日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 破除想当然
    • 84 Largest Rectangle in Histogram
      • 85 Maximal Rectangle
        • 221 Maximal Square
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档