LeetCode 42.接雨水(dp + 栈 + 双指针)

问题描述

题目


暴力解法

首先,对于每一列来说,如果它的左边、右边都存在比自己高的柱子,那么它一定能储水,储水量等同于左边和右边最大值中较小的那个值减去自身高度的差。有了这个思路,我们就可以通过暴力遍历的方法解决该问题:

public int trap(int[] height) {
    int result = 0;
    int length = height.length;
    // 最左和最右的柱子一定不能储水,无需遍历
    for (int i = 1; i < length - 1; i++) {
        int val = height[i], left = val, right = val;
        // 寻找左边最高柱子
        for (int j = 0; j < i; j++) {
            if (height[j] > left) {
                left = height[j];
            }
        }
        // 寻找右边最高柱子
        for (int j = i + 1; j < length; j++) {
            if (height[j] > right) {
                right = height[j];
            }
        }
        result = result + (Math.min(left, right) - val);
    }
    return result;
}

dp 解法

通过上述暴力解法我们得知,每一列储水的量只由左边最高以及右边最高的柱子决定,而最高柱子的计算又存在一种规则:假设第 i 列右边最高柱子的高度为 x,那么第 i - 1 列右边最高柱子的高度就为 Math.max( 第 i 列的高度 ,x),也就是说第 i 列左边最高柱子和右边最高柱子和第 i+1 列、i-1 列存在计算关系,因此我们可以通过 dp 优化暴力解法:

public int trap2(int[] height) {
    int result = 0, length = height.length;
    int[] left = new int[length];
    int[] right = new int[length];
    // 计算第i列左边最高柱子高度
    for (int i = 1; i < length - 1; i++) {
        left[i] = Math.max(left[i - 1], height[i - 1]);
    }
    // 计算第i列右边最高柱子高度
    for (int i = length - 2; i > 0; i--) {
        right[i] = Math.max(right[i + 1], height[i + 1]);
    }
    // 遍历计算
    for (int i = 1; i < length - 1; i++) {
        int temp = Math.min(left[i], right[i]) - height[i];
        result += temp > 0 ? temp : 0;
    }
    return result;
}

双指针解法

上面的 dp 解法提醒我们,当前列的储水量只由左边右边柱子最高值的较小值决定,假设此时我们已经知道右边柱子更高一些,那么当前列的最大储水量就只由左边柱子高度决定。此时我们可以通过双指针分别从两头开始遍历,如果右边的柱子高,那么左引用向右遍历,向右遍历的同时也就是在提示:右边存在更高的柱子,当前列的储水量就由左边最高列来决定

public int trap3(int[] height) {
    int result = 0, leftMax = 0, rightMax = 0;
    int left = 0, right = height.length - 1;
    while (left < right) {
    	// 如果左边小于右边,说明较高的那一列在右边
    	// 这里虽然不是右边的最高列,但已经比当前列左边任何一列高了
        if (height[left] < height[right]) {
        	// 判断当前列是否大于左边最高列,如果是,当前列不能储水
            if (height[left] > leftMax) {
                leftMax = height[left++];
            // 当前列的储水量由左边最高列决定
            } else {
                result += leftMax - height[left++];
            }
        // 右边高同理
        } else {
            if (height[right] > rightMax) {
                rightMax = height[right--];
            } else {
                result += rightMax - height[right--];
            }
        }
    }
    return result;
}

栈解法

前三种解题思路都是根据列来计算,除了这种方式外,还可以使用行的方式来计算。如果第 i 列 比 第 i - 5 列高,并且第 i - 5 列比 i - 6 列低,并且我们确定第 i - 4 列 到 第 i - 1 列都比 i - 5 列低,那么这行可以储水的量就是:长度5乘以第 i 列高和第 i - 6 列高中较小的那个和 i - 5 列高的差值。因此我们可以使用栈将参数数组的下标入栈,方便计算两列之间的距离,如果下一列比栈顶元素高,就向前计算当前行可以储多少水。

public int trap4(int[] height) {
    int result = 0, temp = 0;
    Stack<Integer> stack = new Stack<Integer>();
    while (temp < height.length) {
    	// 1.首先栈不能为空,其次栈顶元素对应列的高小于遍历列的高
    	// 2.此时要遍历到栈为空或者栈顶元素比当前列高为止
        while (!stack.isEmpty() && height[temp] > height[stack.peek()]) {
        	// 取出栈顶元素队列应的下标
            int top = stack.pop();
            // 如果出栈后栈为空,说明栈顶列要么是首列,要么比前面所有列都高,无法储水
            if (stack.isEmpty()) {
                break;
            }
            // 计算当前列和栈顶元素列的距离
            int distance = temp - stack.peek() - 1;
            // 计算可以存储的高度,用当前高度和栈顶元素减去出栈列的高度
            int boundLength = Math.min(height[temp], height[stack.peek()]) - height[top];
            result += distance * boundLength;
        }
        // 遍历下标
        stack.push(temp++);
    }
    return result;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值