摘要:我先通过堆栈的方法,找到一个封闭区间,该区间可以盛水,该区间的右节点可以作为下一个封闭区间的起点。思路三堆栈的聪明使用在这里,堆栈允许我们渐进的通过横向分割而非之前传统的纵向分割的方式来累加计算盛水量。
题目要求
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. For example, Given [0,1,0,2,1,0,1,3,2,1,2,1], return 6.
假设这些是一些间隔的木板,问最多能够装多少水。也就是一个区域性的短板问题。其实一个区间能够乘的最大水量,取决于它的左右最近且最高的木板的长度。当然除了通过多个区间的和来计算总体的盛水量,还可以通过横向的划分来计算盛水量。这些将在接下来中的代码一一分析。官方也提供了一些答案,这里将给出相应的java实现的版本。
我的思路这里先讲一讲我在拿到这个题目时候的思路。我先通过堆栈的方法,找到一个封闭区间,该区间可以盛水,该区间的右节点可以作为下一个封闭区间的起点。这种方法思路非常直接,但是并不是高效的计算机思维。
堆栈的方法如下:
//使用堆栈,分别存储值和下标 public int trap(int[] height) { int length = height.length; if(length<=2){ return 0; } Stacks = new Stack (); Stack index = new Stack (); s.push(0); index.push(1); int leftMost = 0; int result = 0; for(int i = 0 ; i = leftMost){ while(!s.isEmpty()){ result += (leftMost - s.pop()) * index.pop(); } s.push(currentVal); index.push(1); leftMost = currentVal; }else{ //如果当前值比最左值小,则说明该盛水区间仍然没到最右点 int count = 1; //将所有比当前值小的区间填满,并将水平区间的个数插入栈中 while(currentVal > s.peek()){ count += index.peek(); result += (currentVal - s.pop()) * index.pop(); } s.push(currentVal); index.push(count); } } return result; }
使用双指针代替堆栈提高些许性能
//双指针 不使用堆栈 public int trap3(int[] height) { int length = height.length; if(length<=2){ return 0; } //获得可以盛水的区间 int startIndex = 0; while(startIndexheight[startIndex]){ for(int i = index-1 ; i > startIndex ; i--){ result += (height[startIndex] - height[i]); } startIndex = index; }else{ for(int i = index ; i>0 && height[i] > height[i-1] ; i--){ result += (height[i] - height[i-1]); height[i-1] = height[i]; } } } return result; }
这里会降低代码效率的部分主要在于,对于盛水区间的高度的计算太冗杂了。只要获得左右木板高度的最小值,就是当前区间可以盛水的最大高度。在这里最大的问题就在于获得最远区间后,需要对区间内的小区间逐个遍历。这种方法虽然一次遍历数组就可以实现,但是还需要在子区间中反复计算才可以。
思路一:强行遍历从整个数组的角度看来,如果找到区间左侧的最大值leftMax,以及区间右侧的最大值rightMax,就可以知道当前区间的盛水高度为Math.min(leftMax,rightMax)-height[i]。也就是传说中的木桶理论,短板决定盛水的高度。这样的话,代表在每个区间计算盛水值的时候,需要遍历整个数组。遍历整个数组,则需要O(n的平方)的时间复杂度。
这里的代码实现并不难,可以直接参考官方提供的C++方法。
思路一中,每一次对一个区间都要遍历整个数组才能获得左右最大值。但是其实,从左往右遍历一次数组,可以获得各个区间的leftMax。同理,从右往左遍历可以获得各个区间的rightMax。将这两个值都存在数组中,并对数组进行遍历,计算各个区间的盛水高度。时间复杂度为O(n),代码如下:
public int trap3(int[] height){ int length = height.length; //leftMax数组 int[] left = new int[length]; //rightMax数组 int[] right = new int[length]; int leftMax = 0; int rightMax = 0; for(int i = 0 ; i该思路的官方讲解请戳这里
思路三:堆栈的聪明使用
这里涉及了一个解题思路,叫做Dynamic Programming。这在我之前的博客中也有所提及,但是我仍然对这个概念比较模糊。这里有一个非常好的解答帖子供大家参考。笼统的来说,就是利用已知的解答来帮助解决目标问题。看上去好像是一句废话,但是具体实践中有多重形式,例如递归,自顶向下编码和自底向上编码等等。这些概念还是需要通过大量的题目和代码来感受啊。在这里,堆栈允许我们渐进的通过横向分割而非之前传统的纵向分割的方式来累加计算盛水量。一旦当前区间的高度超过栈顶的元素,就代表栈顶元素有一个右边界。鉴于栈中的元素都是递减的,所以如果存在一个比栈顶元素大的栈中元素,则一定可以确定该区间的盛水量。代码如下:
public int trap4(int[] height){ int length = height.length; int result = 0, current = 0; Stack思路四:双指针的进阶s = new Stack (); while(current < length){ while(!s.isEmpty() && height[current] > height[s.peek()]){ int top = s.pop(); if(s.isEmpty()){ break; } //获得两个节点之间的宽度 int distance = current - s.peek() - 1; int tempHeight = Math.min(height[current], height[s.peek()]) - height[top]; result += tempHeight * distance; } s.push(current++); } return result; } 这里要从更高的高度来看这道题目,原文如下:
这里的意思是这样的:
经过实践证明,如果当前区间的leftMax换句话说,假设能找到任意一个比当前区间高度值大的值,并且假设该值位于当前区间的右侧,那么该区间的盛水高度由leftMax决定。同理,如果该值位于当前区间的左侧,那么该区间的盛水高度由rightMax决定。 这个结论可以使用反证法证明。
假设能找到任意一个比当前区间高度值大的值,并且假设该值位于当前区间的右侧,则存在满足这样一个区间,该区间盛水高度由rightMax决定。
这说明,该区间的leftMax大于rightMax。假设当前区间下标为i,则一定存在一个j<=i,height[j]=leftMax。其实,纵观双指针的遍历,我们可以知道,双指针最终会停在高度最高的区间,即height[index] = Max(leftMax, rightMax)上。每一次遍历都会将两个指针向相对较高的位置移动。
所以一旦左指针遍历到刚才的j节点,因为当前右指针指向的值都小于leftMax(rightMax代码实现如下: public int trap5(int[] height) { int left = 0; int right = height.length - 1; int result = 0; int leftMax=0, rightMax=0; while(left < right){ if(height[left] < height[right]){ leftMax = Math.max(height[left], leftMax); result += leftMax - height[left]; left++; }else{ rightMax = Math.max(height[right], rightMax); result += rightMax - height[right]; right--; } } return result; }
想要了解更多开发技术,面试教程以及互联网公司内推,欢迎关注我的微信公众号!将会不定期的发放福利哦~
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/70137.html
摘要:复杂度思路因为蓄水多少取决于比较短的那块板的长度。代码复杂度思路考虑说明时候需要计算蓄水量当的时候,需要计算能储存的水的多少。每次还需要取出一个作为中间值。如果则一直向里面压进去值,不需要直接计算。 Leetcode[42] Trapping Rain Water Given n non-negative integers representing an elevation map ...
摘要:一题目接雨水给定个非负整数表示每个宽度为的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。上面是由数组表示的高度图,在这种情况下,可以接个单位的雨水蓝色部分表示雨水。提交,答案错误。出错的测试用例为。 做有意思的题是要付出代价的,代价就是死活做不出来。 一、题目 接雨水: 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。show...
摘要:从右向左遍历时,记录下上次右边的峰值,如果左边一直没有比这个峰值高的,就加上这些差值。难点在于,当两个指针遍历到相邻的峰时,我们要选取较小的那个峰值来计算差值。所以,我们在遍历左指针或者右指针之前,要先判断左右两个峰值的大小。 Trapping Rain Water Given n non-negative integers representing an elevation map ...
407. Trapping Rain Water II 题目链接:https://leetcode.com/problems... 参考discussion里的解法:https://discuss.leetcode.com/... 参考博客里的解释:http://www.cnblogs.com/grandy... public class Solution { public int tra...
摘要:一种是利用去找同一层的两个边,不断累加寄存。双指针法的思想先找到左右两边的第一个峰值作为参照位,然后分别向后向前每一步增加该位与参照位在这一位的差值,加入,直到下一个峰值,再更新为新的参照位。 Problem Given n non-negative integers representing an elevation map where the width of each bar i...
阅读 2055·2021-11-24 10:28
阅读 1093·2021-10-12 10:12
阅读 3310·2021-09-22 15:21
阅读 662·2021-08-30 09:44
阅读 1867·2021-07-23 11:20
阅读 1124·2019-08-30 15:56
阅读 1721·2019-08-30 15:44
阅读 1467·2019-08-30 13:55