从暴力优化到双指针,明明白白
这道题的最优解法是左右双指针法。双指针法的难点在于难于想到,难以证明。接下来将一步一步地从暴力解法优化到双指针法。证明也就很简单了。
暴力解法
找出每一种情况,求出盛水值,最大的就是答案。
i
指向左挡板,从第一块到遍历倒数第二块。j
指向右挡板,从倒数第一块遍历到i后面那一块。res
保存最大盛水值。- 返回
res
。
代码:
//cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
int maxArea(vector<int>& height) {
if(height.size() <= 1) return 0;
int res = 0;//保存结果
for(int i = 0; i < height.size() - 1; i++)//以i为左挡板,从O开始
{
for(int j = height.size() - 1; j > i; j--)//以j为右挡板,从height.size() - 1开始
{
int L = j - i;//底边长度
int H = min(height[i], height[j]);//对短的板子为高
res = max(res, L * H);//取最大值
}
}
return res;
}
};
int main()
{
int n;
vector<int> height;
cin >> n;
while(n--)
{
int he;
cin >> he;
height.push_back(he);
}
Solution S;
cout << S.maxArea(height);
}
用S[l,r]
表示以第l块板为左挡板,第r
块板为右挡板的盛水值。S[l, r]
就等于min(height[l], height[r]) * (r - l)
。
以输入[1,8,6,2,5,4,8,3,7]
为例,共8块挡板,看看都计算了哪些值:
解题超时
优化一:
- 开始时,
l
指向第0块板子,r
指最后一块板子。S[l, r]=min(1, 7) * (8 - 0) = 8。
- 向内移动指向较长挡板的r指针,盛水面积不会变大。向内移动r指针的时候,盛水值
S[l, r] = min(height[l], height[r]) * (r - l)。min(height[l], height[r])
不会大于height[l]
,也就是不会大于7。(r - l)
会随着r
内移减小。所以向内移动r
指针的时候,盛水值不可能变大。也就是S[0,8]
肯定大于S[0,7],S[0,6],S[0,5],S[0,4],S[0,3],S[0,2],S[0,1]
。因此知道了以height[0]为左挡板的最大盛水值。以后计算就不用考虑height[0]了
-
子问题就变成了:在[8,6,2,5,4,8,3,7]中求出最大盛水值,然后与刚才的求出的以height[0]为左挡板的最大盛水值比较大小,大的为答案。
-
子问题求解时,用
l
指向指向第0块挡板,也就是height[1],r指最后一块挡板,也就是height[8]
。S[l, r]=min(8, 7) * (8 - 1) = 56
。这个时候,如果向内移动指向较长挡板的l指针,盛水面积不会变大,因为向内移动l指针的时候,盛水值S[l, r] = min(height[l], height[r]) * (r - l)
。min(height[l]
,height[r])
不会大于height[r]
,也就是不会大于7。(r - l)
会随着l内移减小。所以向内移动l
指针的时候,盛水值不可能变大。也就是S[1,8]
肯定大于S[2,8],S[3,8],S[4,8],S[5,8],S[6,8],S[7,8]
,就知道了以height[8]
为右挡板的最大盛水值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tpUp17ta-1607612206835)(https://cdn.acwing.com/media/article/image/2020/12/08/55289_1d04d89839-第二次缩小.jpg)] -
子问题可以再次缩小,就变成了:在
[8,6,2,5,4,8,3]
中求出最大盛水值,然后与刚才的求出的以height[0]
为左挡板的最大盛水值,以height[8]
为右挡板的最大盛水值比较大小,大的为答案。 -
以此类推,每次就能求出以最外侧两个挡板中,短的挡板为边界的最大值。然后再一次缩小问题。就不需要计算所有的情况了,只需要计算出每块挡板为边界的最大值,然后求出其中的最大值,就是答案。
-
这样下去,求解空间就变为了:
代码
//cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
int maxArea(vector<int>& height) {
if(height.size() <= 1) return 0;
int res = 0;//保存答案
int l = 0, r = height.size() - 1;//开始时,l指向最左边的挡板,r指向最右边的挡板
while(l < r)//如果l,r之间还有挡板
{
res = max(min(height[l], height[r]) * (r - l), res);//计算盛水值
if(height[l] <= height [r])//谁小谁以后就不用再考虑
l++;
else
r--;
}
return res;
}
};
int main()
{
int n;
vector<int> height;
cin >> n;
while(n--)
{
int he;
cin >> he;
height.push_back(he);
}
Solution S;
cout << S.maxArea(height);
}
时间上,l,r指针遍历一遍,所以时间复杂度是O(n)。空间上,没有开辟与输入有关的空间,所以空间复杂度是O(1)。
觉得还不错,欢迎点赞、收藏评论~
欢迎关注公众号,往期内容也很精彩。每周更新面试高频算法题。从思路,代码,时间复杂度等多方面进行分析。
后台点击电子书可以获得:halfrost大神的孔雀书,经典巨著:算法导论。