接雨水问题(Trapping Rain Water)

先说一个整体思路:无论是用什么算法,其实整体思路是一样的,只是实现有差别。对于每个元素(画图中是柱子)它上面能不能放雨水,能放多少雨水,是由这个元素左右两边的最大元素决定的,min(left_max, right_max)- 当前元素的高度。

进一步简化一下,只要发现左右两边最大元素的小的那个元素就可以了,也就是说,我们遍历的方向上,在远端有元素大于当前值,当前元素的可能蓄水值就是由这个方向的最大元素决定的。

我们可以采用暴力解法,遍历数组,每个数组元素,从左遍历一遍找到左边最大的元素,从右遍历一遍找到右边最大的元素,min(left_max, right_max)- 当前元素的高度,每个元素都遍历完就得到了结果。不过这种嵌套循环,时间复杂度是O(n^{2})。

采用动态规划:

先算出每个元素的left_max和right_max,再遍历数组,相当于把嵌套遍历拆分成并行遍历,还有就是采用了memoization优化技术,避免重复计算。

class Solution {
    public int trap(int[] height) {
        if(height == null || height.length == 0) return 0;
        int size = height.length;
        
        int[] left_maxArr = new int[size];
        left_maxArr[0] = height[0];
        for (int i = 1; i < size; i++) {
            left_maxArr[i] = Math.max(height[i], left_maxArr[i-1]);
        }
                            
        int[] right_maxArr = new int[size];
        right_maxArr[size-1] = height[size - 1];
        for (int i = size - 2; i >= 0; i--) {
            right_maxArr[i] = Math.max(height[i], right_maxArr[i+1]);
        }
        
        int result = 0;
        for (int i = 1; i < size - 1; i++) {
            result += Math.min(left_maxArr[i], right_maxArr[i]) - height[i];
        }
        return result;
    }
}

时间复杂度O(n),空间复杂度O(n)

采用two-pointer technique:

双指针从两头开始,如果height[left] < height[right],说明从左向右,右边有大于当前值的元素,如果当前值小于目前的left_max说明,它是有蓄水能力的,蓄水的值为left_max-height[left],如果当前值大于等于left_max,说明不能蓄水,这个很好理解,left_max值更新成height[left]。这里有个疑问,为啥left_max是左右两边中小的值呢?这是因为left_max的是height[left]更新的,height[left]更新left_max之前也是小于height[right],而right_max最小也是height[right]。如果height[left] >= height[right],说明从右向左,改变方向。

class Solution {
    public int trap(int[] height) {
        if (height == null || height.length == 0) return 0;
        int left = 0;
        int right = height.length - 1;
        int left_max = 0, right_max = 0;
        int result = 0;
        while (left < right) {
            if (height[left] < height[right]) {
                if (height[left] >= left_max)
                    left_max = height[left];
                else 
                    result += left_max - height[left]; 
                
                ++left;
            } else {
                if (height[right] >= right_max) 
                    right_max = height[right]; 
                else 
                    result += right_max - height[right];
                --right;
            }
        }
        return result;
    }
}

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

 

上面的双指针实现方案,不具有抽象概括性,下面的实现方式更好些

class Solution {
    public int trap(int[] height) {
        if (height == null || height.length == 0) return 0;
        int result = 0, left = 0, right = height.length - 1;
        //1.确定边界(只有两个边界)    
        while (left < right) {
            //2.找到边界中最小的一个
            if (height[left] <= height[right]) { //left指向的边界是最小的
                if (left + 1 < right) { //不能超出边界
                    //3.求最小边界的邻居(neighbor)的蓄水能力,只有一个neighbor
                    result += Math.max(0, height[left] - height[left + 1]);
                    //4.更新边界
                    height[left + 1] = Math.max(height[left], height[left + 1]);
                }
                ++left;
            } else { //right指向的边界是最小的
                if (right - 1 > left) {
                    result += Math.max(0, height[right] - height[right - 1]);
                    height[right - 1] = Math.max(height[right], height[right - 1]);
                }
                --right;
            }
        }
        return result;
    }
}

 

42. Trapping Rain Water

 

接雨水问题可以扩展到三维,这种情况更符合实际,解题思路和二维的接雨水问题双指针差不多

407. Trapping Rain Water II

解法说明

class Solution {
    public int trapRainWater(int[][] heightMap) {
        if (heightMap == null || heightMap.length == 0) return 0;
        int m = heightMap.length;
        int n = heightMap[0].length;
        int res = 0;
        
        PriorityQueue<int[]> queue = new PriorityQueue<>((a, b) -> a[2] - b[2]);
        boolean[][] visited = new boolean[m][n];
        
        for (int i = 0; i < m; i++) {
            queue.offer(new int[] {i, 0, heightMap[i][0]});
            queue.offer(new int[] {i, n - 1, heightMap[i][n - 1]});
            visited[i][0] = visited[i][n-1] = true;
        }
        
        for (int i = 1; i < n - 1; i++) {
            queue.offer(new int[] {0, i, heightMap[0][i]});
            queue.offer(new int[] {m-1, i, heightMap[m-1][i]});
            visited[0][i] = visited[m-1][i] = true;
        }
        
        int[][] direcs = new int[][] {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        while (!queue.isEmpty()) {
            int[] min = queue.poll();
            for (int[] dir : direcs) {
                int row = min[0] + dir[0];
                int col = min[1] + dir[1];
                if (row < 0 || row >= m || col < 0 || col >= n || visited[row][col]) continue;
                res += Math.max(0, min[2]-heightMap[row][col]);
                queue.offer(new int[] {row, col, Math.max(min[2], heightMap[row][col])});
                visited[row][col] = true;
            }
        }
        return res;
    }
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值