42.Trapping Rain Water---dp,stack,两指针
题目链接:https://leetcode.com/problems/trapping-rain-water/description/
题目大意:与84题做比较,在直方图中计算其蓄水能力。例子如下:
法一(借鉴):暴力,还是很难想到的,需要推理数学功底。因为这里暴力的前提条件是:计算每个点的蓄水能力,相当于求解,min(每个点左边的最大高度,每个点右边的最大高度)-当前值。也就是这里,把一整个蓄水池的分解成一个点一个点的蓄水能力。代码如下(耗时150ms):
1 public int trap(int[] height) { 2 int res = 0; 3 for(int i = 0; i < height.length; i++) { 4 int left = height[i], right = height[i]; 5 //找左边最大高度 6 for(int j = i - 1; j >= 0; j--) { 7 left = Math.max(left, height[j]); 8 } 9 //找右边最大高度 10 for(int j = i + 1; j < height.length; j++) { 11 right = Math.max(right, height[j]); 12 } 13 //计算蓄水 14 res += Math.min(left, right) - height[i]; 15 } 16 return res; 17 }
法二(借鉴):一维dp,思想与上面暴力相同,都是取左右两边高度的较小者然后与当前值比较,如果大,则可以蓄水。代码如下(耗时22ms):
1 public int trap(int[] height) { 2 int dp[] = new int[height.length]; 3 int ma = 0, res = 0; 4 //记录i值左边的最大高度 5 for(int i = 0; i < height.length; i++) { 6 dp[i] = ma; 7 //更新左边最大高度 8 ma = Math.max(ma, height[i]); 9 } 10 //更新计算i值右边的最大高度 11 ma = 0; 12 for(int i = height.length - 1; i >= 0; i--) { 13 //在左边和右边最大高度中取较小者 14 dp[i] = Math.min(dp[i], ma); 15 //更新右边 16 ma = Math.max(ma, height[i]); 17 //如果两边高度比当前高度高,则表示可以蓄水 18 if(dp[i] > height[i]) { 19 res += dp[i] - height[i]; 20 } 21 } 22 return res; 23 }
法三(借鉴):stack,与84题stack做法比较,此题在压栈的时候是降序高度压栈,当当前高度>s.peek()时,可以蓄水,栈顶第一个元素为坑最低高度,栈顶第二个元素为左边界,当前高度为右边界,坑深为min(左高度,右高度)-坑最低高度。代码如下(耗时30ms):
1 //入栈递减高度,当当前高度>s.peek()时,可以蓄水,栈顶第一个元素为坑最低高度,栈顶第二个元素为左边界,当前高度为右边界,坑深为min(左高度,右高度)-坑最低高度 2 public int trap(int[] height) { 3 Stack<Integer> s = new Stack<Integer>(); 4 int res = 0; 5 for(int i = 0; i < height.length; i++) { 6 //循环查找在当前高度下,是否可以蓄水 7 while(!s.isEmpty() && height[i] > height[s.peek()]) { 8 int cur = s.pop(); 9 //如果栈空,说明没有左边界,没法蓄水,所以直接break 10 if(s.isEmpty()) { 11 break; 12 } 13 //计算蓄水,此时不再是按照前面的方法,分点计算竖状蓄水能力,而是按照整块的蓄水能力计算的,也就是普通思维的按照蓄水池的长*宽来计算的 14 res += (i - s.peek() - 1) * (Math.min(height[s.peek()], height[i]) - height[cur]); 15 } 16 //如果比栈顶高度小,压栈 17 s.push(i); 18 } 19 return res; 20 }
法四(借鉴):两个指针移动,比较左指针指向的高度和右指针指向的高度,记录较小者,如果左指针指向的较小,则从左往右遍历,如果右指针指向的较小,则从右往左遍历,如果遍历到的当前值比较小者小,则表示可以蓄水:较小者-当前值。代码如下(22ms):
1 //定义两个指针,左指针与右指针的值进行比较,记录较小者,如果左指针较小,则从左往右遍历,如果右指针较小,则从右往左遍历,如果遍历到的值比较小者小,则表示可以蓄水:较小者-当前值 2 public int trap(int[] height) { 3 int left = 0, right = height.length - 1, mi = 0, res = 0; 4 while(left < right) { 5 //如果左指针较小,从左往右遍历 6 if(height[left] < height[right]) { 7 mi = height[left++]; 8 while(left < right && height[left] < mi) { 9 res += mi - height[left++]; 10 } 11 } 12 //如果右指针较小,从右往左遍历 13 else { 14 mi = height[right--]; 15 while(left < right && height[right] < mi) { 16 res += mi - height[right--]; 17 } 18 } 19 } 20 return res; 21 }