本文共 5577 字,大约阅读时间需要 18 分钟。
Container With Most Water
Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, ai).n vertical lines are drawn such that the two endpoints of line i is at (i, ai) and (i, 0). Find two lines, which together with x-axis forms a container, such that the container contains the most water.
Note: You may not slant the container.
算法1:枚举容器的两个边界,时间复杂度O(n^2)。大数据超时
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Solution { public : int maxArea(vector< int > &height) { int res = 0, n = height.size(); for ( int i = 0; i < n; i++) //左边界 for ( int j = i+1; j < n; j++) //右边界 { int tmp = (j-i)*min(height[i],height[j]); if (res < tmp)res = tmp; } return res; } }; |
对上面的稍加改进,根据当前的已经计算出来的结果以及左边界的值,可以算出容器右边界的下界。不过还是超时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Solution { public : int maxArea(vector< int > &height) { int res = 0, n = height.size(); for ( int i = 0; i < n; i++) //左边界 { if (height[i] == 0) continue ; for ( int j = max(i+1, res/height[i]+i); j < n; j++) //右边界 { int tmp = (j-i)*min(height[i],height[j]); if (res < tmp)res = tmp; } } return res; } }; |
算法2:时间复杂度O(nlogn)。
构建结构体包含height和height在原数组中的位置
struct Node
{ int height; int index;};
对该结构体数组按照height的值递增排序,假设排序后的数组为vec.
假设f[i] 表示数组vec[i,i+1,…]内所有height按照原来的位置顺序排列好以后的最大水量
那么f[i-1]可以在O(1)时间内计算出来:因为vec[i-1].height 小于vec[i,i+1,…]内的所有height,所以以vec[i-1].index为边界的容器高度为vec[i-1].height,最大水量只需要分别计算vec[i,i+1,…]内按原始位置排列最前面和最后面的height,取两者的较大值。即下图中,黑色是最低的,要计算以黑色为边界的容器的最大水量,只需要分别和第一个和最后一个计算,去两者较大值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | class Solution { struct Node { int height; int index; Node( int h, int i):height(h),index(i){} Node(){} bool operator < ( const Node &a) const { return height < a.height; } }; public : int maxArea(vector< int > &height) { int res = 0, n = height.size(); if (n <= 1) return 0; vector<Node>vec(n); for ( int i = 0; i < n; i++) { vec[i].index = i; vec[i].height = height[i]; } sort(vec.begin(), vec.end()); int start = vec[n-1].index, end = start; //记录已经处理完的height的原始位置的左右端点 for ( int i = n-2; i >= 0 ; i--) { start = min(start, vec[i].index); end = max(end, vec[i].index); res = max(res, max(vec[i].height*(vec[i].index - start), vec[i].height*(end - vec[i].index))); } return res; } }; |
算法3:时间复杂度O(n),两个指针i, j分别从前后向中间移动,两个指针分别表示容器的左右边界。每次迭代用当前的容量更新最大容量,然后把高度小的边界对应的指针往中间移动一位。
正确性证明:由于水的容量是有较小的那个边界决定的,因此某次迭代中,假设height[i] < height[j],那么j 减小肯定不会使水的容量增大,只有i 增加才有可能使水的容量增大。但是会不会有这种可能:当前的i 和 某个k (k > j)是最大容量, 这也是不可能的,因为按照我们的移动规则,既然右指针从k 移动到了j,说明i 的左边一定存在一个边界 m,使m > k,那么[m, k]的容量肯定大于[i, k],所以[i,k]不可能是最大容量。可以参考
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Solution { public : int maxArea(vector< int > &height) { int res = 0, n = height.size(); int left = 0, right = n-1; while (left < right) { res = max(res, (right-left)*min(height[left], height[right])); if (height[left] < height[right]) left++; else right--; } return res; } }; |
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
. 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.
注意和上一题的区别,上一题相当于在每个位置放一个挡板,挡板之间可以蓄水;这一题是放置宽度为1的柱子,柱子上面可以蓄水。
算法4:分析某个柱子,发现该柱子上水的高度和其左右两边的最高柱子有关,设该柱子左边所有柱子中最高的为leftmax,右边所有柱子中最高的为rightmax,如果min(leftmax, rightmax) 大于该柱子的高度,那么该柱子上可以蓄水为min(leftmax, rightmax) - 该柱子高度,如果min(leftmax, rightmax) <= 该柱子高度,则该柱子上没有蓄水。
可以从后往前扫描一遍数组求得某个柱子右边的最高柱子,从前往后扫描一遍数组求得某个柱子左边的最高柱子, 然后按照上面的分析可以求得所有的蓄水量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class Solution { public : int trap( int A[], int n) { int res = 0; vector< int >rightMax(n); //柱子右边最大的柱子高度 int maxv = 0; for ( int i = n-1; i >= 0; i--) { rightMax[i] = maxv; maxv < A[i] ? maxv = A[i] : maxv; } maxv = 0; int conHeight; for ( int i = 0; i < n; i++) { //此时maxv为柱子i左边最大的柱子高度 conHeight = min(maxv, rightMax[i]); if (conHeight > A[i]) res += conHeight - A[i]; maxv < A[i] ? maxv = A[i] : maxv; } return res; } }; |
上面的代码时间空间复杂度都是O(n),扫描了两次数组
算法5:可以换一种思路,如下图,如果我们求出了两个红色框的面积,然后再减去框内黑色柱子的面积,就是水的面积了,时间复杂度O(N),空间O(1), 数组扫描2次
如何求红色框内的面积呢,我们先求出最大的柱子高度,然后左右分开求。求面积是是一层一层的累加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | class Solution { public : int trap( int A[], int n) { int maxHeight = 0, maxIdx = 0; for ( int i = 0; i < n; i++) //求最大高度 if (A[i] > maxHeight) { maxHeight = A[i]; maxIdx = i; } int height = 0; int pillarArea = 0; //柱子面积 int totalArea = 0; //总面积 for ( int i = 0; i < maxIdx; i++) { if (A[i] > height) { totalArea += (A[i] - height)*(maxIdx - i); height = A[i]; } pillarArea += A[i]; } height = 0; for ( int i = n-1; i > maxIdx; i--) { if (A[i] > height) { totalArea += (A[i] - height)*(i - maxIdx); height = A[i]; } pillarArea += A[i]; } return totalArea - pillarArea; } }; |
算法6:参考,也是和算法5一样求面积,但是这里利用上一题的左右指针思想,只需要扫描一遍数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | class Solution { public : int trap( int A[], int n) { int left = 0, right = n-1; int totalArea = 0, pillarArea = 0, height = 0; while (left <= right) { if (A[left] < A[right]) { if (A[left] > height) { totalArea += (A[left]-height)*(right - left + 1); height = A[left]; } pillarArea += A[left]; left++; } else { if (A[right] > height) { totalArea += (A[right]-height)*(right - left + 1); height = A[right]; } pillarArea += A[right]; right--; } } return totalArea - pillarArea; } }; |