• LeetCode刷题05-42. 接雨水 tag 数组 hard


    四种解法

    一、左右指针对撞

    思路:左右比较取小值,将lower与记录的水位线进行比较,如果水位线高于lower,则水位线设为level=lower,然后记录该处可存水量water+=level-lower。这里相当于,时刻记录左右两边的最小值作为水位线,然后用水位线减去本处高度作为存水量。

    class Solution {
    public:
        int trap(vector<int>& height) {
            int n=height.size();
            int l=0,r=n-1,lower=0,level=0;
            int water=0;
            while(l<r)
            {
                lower=min(height[l],height[r]);
                if(height[l]<height[r])
                    l++;
                else 
                    r--;
                level=max(lower,level);
                water+=level-lower;
            }
            return water;
        }
    };

    二、按照列求
    思路
    对每一列来说,都判断自己这一列能不能装水,如何判断呢?
    看自己左边最高的墙(不包括自己)和右边最高的墙(不包括自己),根据短板效应取这俩中更小的那个值lowWall
    lowWall比自己高那才可能出现凹坑才能存水对吧,旁边的比自己矮那不流出去了
    然后计算这一列上存了多少水
    首先两端的墙不用考虑,存不了,直接从第二个看
    比如下面红色这一列能存几个水呢?

    1.红色自己高度是1,左边最高是绿色,高度为0,右边最高是紫色,值为3,根据短板效应取lowWall = 0,
    2.lowWall没有自己高,装不了


    来看个能装的例子,下图我们来看红色框里这一列,它能装多少水呢?
    首先,左边最高的墙是绿色,高度为2,右边最高的墙为紫色,高度为3,lowWall取值为2
    lowWall大于红色自身高度1所以能装啊

    那每列能装多少水呢?根据上图看出来,是(lowWall-自身高度)
    最后按照这个方法计算每一列就完事了

    逐句注释的代码实现

    public int trap(int[] height) {
        int sum = 0;
        //最两端的列不用考虑,因为一定不会有水。所以下标从 1 到 length - 2
        for (int i = 1; i < height.length - 1; i++) {
            int max_left = 0;
            //找出左边最高
            for (int j = i - 1; j >= 0; j--) {
                if (height[j] > max_left) {
                    max_left = height[j];
                }
            }
            int max_right = 0;
            //找出右边最高
            for (int j = i + 1; j < height.length; j++) {
                if (height[j] > max_right) {
                    max_right = height[j];
                }
            }
            //找出两端较小的
            int min = Math.min(max_left, max_right);
            //只有较小的一段大于当前列的高度才会有水,其他情况不会有水
            if (min > height[i]) {
                sum = sum + (min - height[i]);
            }
        }
        return sum;
    }

    时间复杂度:O(n²),遍历每一列需要 nn,找出左边最高和右边最高的墙加起来刚好又是一个 n,所以是 n²。
    空间复杂度:O(1)

    三、动态规划(优化刚才的按列)
    思路
    刚才代码中出现了一些重复出现的循环遍历,每次计算新的一列时候,我们都要重新找两边最高的墙,完全没必要啊,利用动态规划的思想,把这些信息放到一个数组里就ok,动态规划的思想很复杂,在这里说不清,以后再详细讲解,反正大概就是把重复的方法归纳起来复用

    逐句注释的代码实现

    public int trap(int[] height) {
        //sum计算总和
        // 新建两个长度和height一样的数组用来存 每一列的左右最高墙在height中的索引
        int sum = 0;
        int[] max_left = new int[height.length];
        int[] max_right = new int[height.length];
        
        //求所有列的左边最高墙  索引都放在max_left里
        for (int i = 1; i < height.length - 1; i++) {
            max_left[i] = Math.max(max_left[i - 1], height[i - 1]);
        }
    
        //求所有列的右边最高墙  索引都放在max_right里
        for (int i = height.length - 2; i >= 0; i--) {
            max_right[i] = Math.max(max_right[i + 1], height[i + 1]);
        }
    
        //计算每一列的能存多少水累加起来   这一步和上面方法没区别
        for (int i = 1; i < height.length - 1; i++) {
            int min = Math.min(max_left[i], max_right[i]);
            if (min > height[i]) {
                sum = sum + (min - height[i]);
            }
        }
        return sum;
    }

    动态规划优化了求一侧最大值的操作,其余代码没变化
    时间复杂度:O(n)
    空间复杂度:O(n),用来保存每一列左边最高的墙和右边最高的墙。
    相比刚才的方法,时间复杂度降了下来,空间复杂度略有上升

    四、双指针(把刚才上升的空间复杂度给降下来成为指标最优的解法)
    思路
    这道题中,可以看到,max_left[ i ] 和 max_right[ i ] 数组中的元素我们其实只用一次,然后就再也不会用到了。所以我们可以不用数组,只用一个元素就行了。

    逐句注释的代码实现
    我们先改造下 max_left

    public int trap(int[] height) {
        int sum = 0;
        int max_left = 0;
    
        //右侧的没改还用数组存
        int[] max_right = new int[height.length];
        for (int i = height.length - 2; i >= 0; i--) {
            max_right[i] = Math.max(max_right[i + 1], height[i + 1]);
        }
    
        //左侧的改了
        for (int i = 1; i < height.length - 1; i++) {
            max_left = Math.max(max_left, height[i - 1]);
            int min = Math.min(max_left, max_right[i]);
            if (min > height[i]) {
                sum = sum + (min - height[i]);
            }
        }
        return sum;
    }

    时间复杂度: O(n)
    空间复杂度:O(1)

    五、栈

    思路

    这道题好像就是想考栈= =
    就是把墙的高度压入栈中,栈顶元素就是上一个墙的高度,如果指针指的当前墙比上一个墙高,那就说明,能存水,没有上一个墙高,那就存不了水,把这个墙放进去
    具体思路都写代码注释里面了,注释已经不能再细了

    逐句注释的代码实现

    public int trap(int[] height) {
    
            /**
             * 初始栈
             * 初始总和
             * 初始指针指向第一个元素
             */
            Stack<Integer> stack = new Stack<>();
            int sum = 0;
            int currIndex = 0;
    
            /**
             * 最后用大循环包围
             */
            while (currIndex < height.length) {
                /**
                 * 如果下一个比栈顶的大,那就进行一系列操作
                 */
                while (!stack.empty() && height[currIndex] > height[stack.peek()]) {
                    /**
                     * 1.首先取出栈顶元素 是计算面积的底
                     * 2.然后刚才的底没用了,出栈,我们要他之前的那个墙(新的栈顶)的高度,和currIndex指针指向的墙比,哪个矮,哪个-底就等于计算面积的高
                     *                  (矮墙-底) X 底边宽 = 面积
                     * 3.底边宽怎么求?  底边宽 = 后面的墙-前面的墙-1
                     * 4.所以最终 sum = 面积 = (矮墙-底) X 底边宽
                     */
    
                    //1.
                    int bottomHeight = height[stack.peek()];
    
                    //2.
                    stack.pop();
                    //这里注意 要是出栈以后 栈空了那就跳出循环进行下一个height里的元素操作
                    if (stack.empty()) break;
                    //go on
                    int lowerWall = Math.min(height[currIndex], height[stack.peek()]);
    
                    //3.
                    int bottomWide = currIndex - stack.peek() - 1;
    
                    //4.
                    sum = sum + (lowerWall - bottomHeight) * bottomWide;
    
    
                }
                /**
                 * 如果下一个比栈顶的小,那就直接入栈
                 */
                stack.push(currIndex);
    
    
                /**
                 * 遍历记得指针往后走
                 */
                currIndex++;
    
    
            }
    
    
            return sum;
        }
    
    }


    ————————————————
    版权声明:本文为CSDN博主「RookieZc」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_41132565/article/details/109160614

    联系方式:emhhbmdfbGlhbmcxOTkxQDEyNi5jb20=
  • 相关阅读:
    大型网站的数据库分割问题。
    大型网站的数据库分割问题。
    分布式集群的Session问题
    大型网站架构设计摘要
    大型网站的架构概要
    公司产品的优势
    java.util.concurrent 学习笔记(2) 线程池基础
    《Java 7 并发编程指南》学习概要 (6) Fork/Join
    《Java 7 并发编程指南》学习概要 (5) 线程池
    《Java 7 并发编程指南》学习概要 (4) 并发集合
  • 原文地址:https://www.cnblogs.com/zl1991/p/14507854.html
Copyright © 2020-2023  润新知