Day49--单调栈--42. 接雨水,84. 柱状图中最大的矩形

Day49–单调栈–42. 接雨水,84. 柱状图中最大的矩形

42. 接雨水

这里已经是hard题目了,要是没想出来,别硬刚。先看题解,解说。做得出来自己做一遍,做不出来抄一遍日后回来再做。

方法:双指针法

思路:

辅助数组记录左/右边最大高度

class Solution {
    public int trap(int[] height) {
        int n = height.length;
        if (n <= 2) {
            return 0;
        }
        int[] maxLeft = new int[n];
        int[] maxRight = new int[n];

        // 记录每个柱子左边柱子最大高度
        maxLeft[0] = height[0];
        for (int i = 1; i < n; i++) {
            maxLeft[i] = Math.max(height[i], maxLeft[i - 1]);
        }
        // 记录每个柱子右边柱子最大高度
        maxRight[n - 1] = height[n - 1];
        for (int i = n - 2; i >= 0; i--) {
            maxRight[i] = Math.max(height[i], maxRight[i + 1]);
        }
        // 求和
        int sum = 0;
        for (int i = 0; i < n; i++) {
            int count = Math.min(maxLeft[i], maxRight[i]) - height[i];
            sum += count;
        }
        return sum;
    }
}

方法:双指针法优化

思路:

  • 双指针优化:更符合双指针的概念。
  • 但是理解起来更烧脑。
  • 因为这是“从两边向中间寻找最值”,而不是原来的,“根据当前列,判断当前列的左右两边的最大最大高度。”
  • 可以这么解释,比如现在在左边,maxLeft < maxRight了,是时候统计左边列的当前装水量了,为什么当前这个水肯定能被装上?右边一定有堵墙吗?——没错,对,就是右边有堵墙,l一直往右走,一定会遇到r,所以也一定会遇到maxRight
// 双指针优化:更符合双指针的概念。
// 但是理解起来更烧脑。
// 因为这是“从两边向中间寻找最值”,而不是原来的,“根据当前列,判断当前列的左右两边的最大最大高度。”
// 可以这么解释,比如现在在左边,maxLeft < maxRight了,是时候统计左边列的当前装水量了,为什么当前这个水肯定能被装上?右边一定有堵墙吗?——没错,对,就是右边有堵墙,l一直往右走,一定会遇到r,所以也一定会遇到maxRight
class Solution {
    public int trap(int[] height) {
        if (height.length <= 2) {
            return 0;
        }
        // 从两边向中间寻找最值
        int maxLeft = height[0];
        int maxRight = height[height.length - 1];
        int left = 1;
        int right = height.length - 2;
        int res = 0;
        while (left <= right) {
            // 不确定上一轮是左边移动还是右边移动,所以两边都需更新最值
            maxLeft = Math.max(maxLeft, height[left]);
            maxRight = Math.max(maxRight, height[right]);
            // 最值较小的一边所能装的水量已定,所以移动较小的一边。
            if (maxLeft < maxRight) {
                res += maxLeft - height[left];
                left++;
            } else {
                res += maxRight - height[right];
                right--;
            }
        }
        return res;
    }
}

方法:单调栈

思路:

寻找右边第一个不单调值,中间就会出现坑

// 单调栈方法:寻找右边第一个不单调值,中间就会出现坑
class Solution {
    public int trap(int[] height) {
        if (height.length <= 2) {
            return 0;
        }
        // 存着下标,计算的时候用下标对应的柱子高度
        Stack<Integer> st = new Stack<>();
        int sum = 0;
        st.push(0);
        for (int i = 1; i < height.length; i++) {
            // 情况一(值比栈顶的小)
            // 栈的大概情况——栈底[5,4,3栈顶
            if (height[i] < height[st.peek()]) {
                st.push(i);
            }
            // 情况二,值相等,只存最新的
            if (height[i] == height[st.peek()]) {
                st.pop();
                st.push(i);
            } else {
                // 情况三,值比栈顶的大。证明栈顶是“水坑”,可以储水
                // 注意这里是while
                while (!st.isEmpty() && height[i] > height[st.peek()]) {
                    // 弹出了一个元素作为mid

                    int mid = st.pop();
                    // 此时的高度:栈顶>height[mid],height[mid]<height[i]
                    // 所以栈里必须还有元素,不然就是边缘的情况,也就是0的位置
                    if (!st.isEmpty()) {
                        // 取高度:min(栈顶位置,i位置),“短板效应”
                        int h = Math.min(height[st.peek()], height[i]) - height[mid];
                        // 取宽度:注意减一,只求中间宽度
                        int w = i - st.peek() - 1;
                        sum += h * w;
                    }
                }
                // 经历了while循环之后,此时i位置的柱子,是小于等于栈顶的,入栈
                st.push(i);
            }
        }
        return sum;
    }
}

84. 柱状图中最大的矩形

记录:这道题,看了题解之后,第二天尝试自己复现,还是不断报错。关于一些边界的处理问题,初始化的问题,要非常小心。

方法:双指针法

思路:

和《接雨水》一样,但是要记录”每个柱子左边第一个小于该柱子的下标“,而不是”左边第一个小于该柱子的高度“

注意:t = minLeftIndex[t];写成t++的话,时间会超过限制。不能一个一个跳。

// 双指针法。辅助数组(记录左边/右边第一个小于该柱子的下标)
class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        int[] minLeftIndex = new int[n];
        int[] minRightIndex = new int[n];

        // 注意这里初始化,防止下面while死循环
        minLeftIndex[0] = -1;
        // 记录每个柱子 左边第一个小于该柱子的下标
        for (int i = 1; i < n; i++) {
            int t = i - 1;
            // 这里不是用if,而是不断向左寻找的过程
            while (t >= 0 && heights[t] >= heights[i]) {
                // 注意:`t = minLeftIndex[t];`写成`t++`的话,时间会超过限制。不能一个一个跳。
                t = minLeftIndex[t];
            }
            minLeftIndex[i] = t;
        }

        // 注意这里初始化,防止下面while死循环
        minRightIndex[n - 1] = n;
        // 记录每个柱子 右边第一个小于该柱子的下标
        for (int i = n - 2; i >= 0; i--) {
            int t = i + 1;
            // 这里不是用if,而是不断向右寻找的过程
            while (t < n && heights[t] >= heights[i]) {
                t = minRightIndex[t];
            }
            minRightIndex[i] = t;
        }
        // 求和
        int result = 0;
        for (int i = 0; i < n; i++) {
            int sum = heights[i] * (minRightIndex[i] - minLeftIndex[i] - 1);
            result = Math.max(sum, result);
        }
        return result;
    }
}

方法:单调栈法

思路:

与《接雨水》相比,栈内的单调性相反。

// 单调栈方法。与《接雨水》相比,栈内的单调性相反。
class Solution {
    public int largestRectangleArea(int[] heights) {
        int res = 0;
        // stack存的是下标
        Deque<Integer> stack = new ArrayDeque<>();

        // 创建新数组,在原数组基础上头部和尾部各加一个0
        int[] newHeights = new int[heights.length + 2];
        for (int i = 0; i < heights.length; i++) {
            newHeights[i + 1] = heights[i];
        }
        newHeights[0] = 0;
        newHeights[newHeights.length - 1] = 0;

        stack.push(0);
        // 第一个元素已经入栈,从下标1开始
        for (int i = 1; i < newHeights.length; i++) {
            // 情况一,当前高度大于左边高度
            if (newHeights[i] > newHeights[stack.peek()]) {
                stack.push(i);

                // 情况二,当前高度等于左边高度
            } else if (newHeights[i] == newHeights[stack.peek()]) {
                stack.pop();
                stack.push(i);
            } else {
                // 情况三,当前高度小于左边高度
                // 注意是while
                while (!stack.isEmpty() && newHeights[i] < newHeights[stack.peek()]) {
                    int mid = stack.pop();
                    if (!stack.isEmpty()) {
                        int left = stack.peek();
                        int right = i;
                        int w = right - left - 1;
                        int h = newHeights[mid];
                        res = Math.max(res, w * h);
                    }
                }
                stack.push(i);
            }
        }
        return res;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值