盛水就是理解为面积的意思吧,令i, j表示数组中元素的下标,对应的矩形长为 j − i j-i j−i,高为 m i n ( a i , a j ) min(a_i, a_j) min(ai,aj),最多盛水即为矩形面积 ( j − i ) ∗ m i n ( a i , a j ) (j-i)*min(a_i, a_j) (j−i)∗min(ai,aj)。
容易让人想到滑动窗口或者双指针,每一对(i,j)都会对应一个面积,我们需要的是最大的面积,如果使用暴力求解,需要对每一个 i 都尝试i后面所有的 j ,时间复杂度为O(n^2)。
暴力法的代码很简单:
class Solution {
public:
int maxArea(vector<int>& height) {
int area = 0;
for(int i = 0; i < height.size(); ++i){
for(int j = i+1; j < height.size(); ++j)
area = max(area, (j-i)*min(height[i], height[j]));
}
return area;
}
};
但是会在倒数第二个测试用例上超过时间限制,说明需要进行优化。我们不能对i后面的每一个j都进行计算,那就优化一下:
class Solution {
public:
int maxArea(vector<int>& height) {
int area = 0;
int left = 0; //用于记录当前最大的左边界
for(int i = 0; i < height.size(); ++i){
if(height[i] < height[left]) continue;
int right = height.size()-1;//用于记录当前最大的右边界
for(int j = height.size()-1; j > i; --j){//从最右边开始
if(height[j] < height[right]) continue;
area = max(area, (j-i)*min(height[i], height[j]));
cout << i << " " << area << endl;
right = j;
}
left = i;
}
return area;
}
};
进行了部分优化,可以提交通过,但是效率一般,而且时间复杂度还是没变,依然是两层循环,两次遍历
有没有一次遍历就可以的思路呢?我们还可以从两边往中间移动,不停地缩小窗口,设i,j分别是左右窗口边界
- 如果height[i] <= height[j]:area = (j-i)*height[i],然后–i
- 如果height[i] > height[j]:area = (j-i)*height[j],然后–j
当 i = = j i == j i==j的时候,计算结束。
class Solution {
public:
int maxArea(vector<int>& height) {
int area = 0;
int i = 0, j = height.size()-1; //分别是窗口的左右边界
while(i < j){
if(height[i] <= height[j])
area = max(area, (j-i)*height[i++]);
else
area = max(area, (j-i)*height[j--]);
}
return area;
}
};
时间复杂度是O(n),一次遍历,运行效率直接起飞,上述代码还可以简化一下,三行代码:
class Solution {
public:
int maxArea(vector<int>& height) {
int area = 0, i = 0, j = height.size()-1; //分别是窗口的左右边界
while(i < j) area = (height[i] <= height[j]) ? max(area, (j-i)*height[i++]) : max(area, (j-i)*height[j--]);
return area;
}
};