LeetCode Top 100 Liked Questions 42. Trapping Rain Water (Java版; Hard)
题目描述
Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.
The above elevation map is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. Thanks Marcos for contributing this image!
Input: [0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6
Example:
class Solution {
public int trap ( int [ ] height) {
int n = height. length;
if ( n<= 2 ) {
return 0 ;
}
int Lmax= height[ 0 ] , Rmax= height[ n- 1 ] ;
int left = 1 , right = n- 2 ;
int res= 0 ;
while ( left<= right) {
if ( Lmax< Rmax) {
if ( Lmax- height[ left] > 0 ) {
res += Lmax- height[ left] ;
}
Lmax = Math. max ( Lmax, height[ left] ) ;
left++ ;
} else {
if ( Rmax- height[ right] > 0 ) {
res += Rmax- height[ right] ;
}
Rmax = Math. max ( Rmax, height[ right] ) ;
right-- ;
}
}
return res;
}
}
第二次做; 除了相向而行的双指针外, 还得用两个变量记录双指针外侧的最大值; 理解height[leftMaxIndex] == height[rightMaxIndex]时,从哪边遍历都行, 比如看看例子[3,0,2,2,3]
class Solution {
public int trap ( int [ ] height) {
int n = height. length;
int left= 0 , right= n- 1 ;
int leftMaxIndex= 0 , rightMaxIndex= n- 1 ;
int res = 0 ;
while ( left<= right) {
int leftMax = height[ leftMaxIndex] ;
int rightMax = height[ rightMaxIndex] ;
if ( leftMax < rightMax) {
if ( leftMax > height[ left] ) {
res += leftMax - height[ left] ;
}
else {
leftMaxIndex = left;
}
left++ ;
}
else {
if ( rightMax > height[ right] ) {
res += rightMax - height[ right] ;
}
else {
rightMaxIndex = right;
}
right-- ;
}
}
return res;
}
}
第二次做; 一列一列地计算, 先用哈希表记录每一列左右两侧的最大值
class Solution {
public int trap ( int [ ] height) {
HashMap< Integer, Integer> leftMax = new HashMap < > ( ) ;
HashMap< Integer, Integer> rightMax = new HashMap < > ( ) ;
int n = height. length;
int max = 0 ;
for ( int i= 1 ; i< n; i++ ) {
if ( height[ i- 1 ] > max)
max = height[ i- 1 ] ;
leftMax. put ( i, max) ;
}
max = 0 ;
for ( int i= n- 2 ; i>= 0 ; i-- ) {
if ( height[ i+ 1 ] > max)
max = height[ i+ 1 ] ;
rightMax. put ( i, max) ;
}
int res = 0 ;
for ( int i= 1 ; i< n- 1 ; i++ ) {
int left = leftMax. get ( i) , right = rightMax. get ( i) ;
res += Math. min ( left, right) > height[ i] ? Math. min ( left, right) - height[ i] : 0 ;
}
return res;
}
}
第一次做, 双指针, 2ms, 在遍历每一根柱子时, 可能从左往右遍历, 也可能从右往左遍历, 举例来说, 如果左墙比右墙低, 就从左往右遍历, 因为此时柱子的存水量最多是左墙的高度; 这道题主要就是理解什么时候从左往右遍历, 什么时候从右往左遍历; 还需要两个变量分别存储左墙和右墙的索引, 注意如何更新墙的索引
class Solution {
public int trap ( int [ ] height) {
if ( height== null || height. length < 3 )
return 0 ;
int leftMaxIndex= 0 , rightMaxIndex= height. length- 1 ;
int sum = 0 ;
int left = 1 , right = height. length- 2 ;
while ( left <= right) {
if ( height[ leftMaxIndex] < height[ rightMaxIndex] ) {
if ( height[ leftMaxIndex] > height[ left] ) {
sum += height[ leftMaxIndex] - height[ left] ;
}
else {
leftMaxIndex = left;
}
left++ ;
}
else {
if ( height[ rightMaxIndex] > height[ right] ) {
sum += height[ rightMaxIndex] - height[ right] ;
}
else {
rightMaxIndex = right;
}
right-- ;
}
}
return sum;
}
}
第一次做,拿空间换时间, 时间复杂度O(N),3ms, 空间复杂度O(N); 一列一列地处理, 不再重新求左右的最大值, 而是保存左右的最大值, 适当地进行更新; 左侧最大值好办, 主要是如何更新右侧最大值? 从右向左遍历! 别老是想着一遍完成任务, 其实两遍遍历,三遍遍历都是可以的, 没有增加时间复杂度的量级
class Solution {
public int trap ( int [ ] height) {
if ( height== null || height. length< 3 )
return 0 ;
int sum= 0 ;
int [ ] leftMax = new int [ height. length] ;
int [ ] rightMax = new int [ height. length] ;
leftMax[ 0 ] = height[ 0 ] ;
for ( int i= 1 ; i< height. length; i++ ) {
leftMax[ i] = Math. max ( height[ i] , leftMax[ i- 1 ] ) ;
}
rightMax[ height. length- 1 ] = height[ height. length- 1 ] ;
for ( int i= height. length- 2 ; i>= 0 ; i-- ) {
rightMax[ i] = Math. max ( height[ i] , rightMax[ i+ 1 ] ) ;
}
for ( int i= 1 ; i< height. length- 1 ; i++ ) {
if ( leftMax[ i- 1 ] > height[ i] && rightMax[ i+ 1 ] > height[ i] )
sum = sum + Math. min ( leftMax[ i- 1 ] , rightMax[ i+ 1 ] ) - height[ i] ;
}
return sum;
}
}
第一次做, 时间复杂度O(N^2), 95ms, 空间复杂度O(1), 一列一列地计算; 对于当前列来说, 只有左侧的最大值和右侧的最大值都大于当前值时可能存水; 这道题耗时的地方在于,考虑每一列时都要重新计算其左右的最大值, 如果能把左右的最大值保存下来就好了!
class Solution {
public int trap ( int [ ] height) {
if ( height== null || height. length< 3 )
return 0 ;
int leftMax= 0 , rightMax= 0 , sum= 0 ;
for ( int i= 1 ; i< height. length- 1 ; i++ ) {
leftMax = getMax ( height, 0 , i- 1 ) ;
if ( leftMax<= height[ i] )
continue ;
rightMax = getMax ( height, i+ 1 , height. length- 1 ) ;
if ( rightMax<= height[ i] )
continue ;
sum = sum + Math. min ( leftMax, rightMax) - height[ i] ;
}
return sum;
}
public int getMax ( int [ ] arr, int i, int j) {
int max = 0 ;
for ( int k= i; k<= j; k++ )
max = Math. max ( max, arr[ k] ) ;
return max;
}
}
题解, 双指针, 不再用数组存储最大值, 只用了两个常量
public int trap ( int [ ] height) {
int sum = 0 ;
int max_left = 0 ;
int max_right = 0 ;
int left = 1 ;
int right = height. length - 2 ;
for ( int i = 1 ; i < height. length - 1 ; i++ ) {
if ( height[ left - 1 ] < height[ right + 1 ] ) {
max_left = Math. max ( max_left, height[ left - 1 ] ) ;
int min = max_left;
if ( min > height[ left] ) {
sum = sum + ( min - height[ left] ) ;
}
left++ ;
} else {
max_right = Math. max ( max_right, height[ right + 1 ] ) ;
int min = max_right;
if ( min > height[ right] ) {
sum = sum + ( min - height[ right] ) ;
}
right-- ;
}
}
return sum;
}
LeetCode优秀例子(https://leetcode.com/problems/trapping-rain-water/discuss/17391/Share-my-short-solution.)