盛最多水的容器(经典)
添加链接描述
解法:
左右指针初始指向数组两端点,记录此时的容量。
每次将高度小的指针向中间移动,每移动一位计算一次容量。
直到相遇,最大容量即为所求!
证明此法可行:
关键是这句:每次将高度小的指针向中间移动
因为此时数字较小的那个指针不可能再作为容器的边界了。所以废弃这个位置向中间移动。
为什么不可能再作为容器的边界了?证明:
代码:
public class Solution {
public int maxArea(int[] height) {
int max=-1;
int i=0,j=height.length-1;
while (i<j){
int area= Math.min(height[i],height[j])*(j-i);
max=Math.max(max,area);
if(height[i]<=height[j]){
i++;
}else {
j--;
}
}
return max;
}
}
接雨水
暴力解法 时间n^2 空间1
对于这种问题,我们不要想整体,而应该去想局部;
具体来说,对于下图位置i,能装下多少水呢?能装 2 格水
为什么位置i最多能盛 2 格水呢?因为,位置i能达到的水柱高度和其左边的最高柱子、右边的最高柱子有关,我们分别称这两个柱子高度为l_max和r_max
;位置 i 最大的水柱高度就是min(l_max, r_max)。
更进一步,对于位置i,能够装的水为:
water[i] = min(
# 左边最高的柱子
max(height[0..i]),
# 右边最高的柱子
max(height[i..end])
) - height[i]
代码:
class Solution {
public int trap(int[] height) {
int ans=0;
int len=height.length;
for (int i=1;i<len-1;i++){
//左右两端不可能装水
int l_max=0,r_max=0;
//计算左最高
for(int j=0;j<=i;j++){
l_max=Math.max(l_max,height[j]);
}
//计算右最高
for(int j=i;j<len;j++){
r_max=Math.max(r_max,height[j]);
}
ans+=Math.min(l_max,r_max)-height[i];// 如果自己就是最高的话,l_max == r_max == height[i]
}
return ans;
}
}
备忘录优化 时间n 空间n
暴力解法中,对每个位置i都要计算r_max和l_max,存在大量重复计算。
优化:开两个数组r_max和l_max
充当备忘录,l_max[i]
表示位置i左边最高的柱子高度,r_max[i]
表示位置i右边最高的柱子高度。预先把这两个数组计算好,避免重复计算。
代码:
class Solution {
public int trap(int[] height) {
if(height.length==0) return 0;
int ans=0;
int len=height.length;
int[] l_max=new int[len];
int[] r_max=new int[len];
l_max[0]=height[0];
r_max[len-1]=height[len-1];
//计算左边最大
for (int i=1;i<len;i++ ){
l_max[i]=Math.max(l_max[i-1],height[i]);
}
//计算右边最大
for (int i=len-2;i>=0;i--){
r_max[i]=Math.max(r_max[i+1],height[i]);
}
//累加结果
for (int i=1;i<len-1;i++){
ans+=Math.min(l_max[i],r_max[i])-height[i];
}
return ans;
}
}
双指针解法 时间n 空间1
用双指针边走边算,节省下空间复杂度。
很难,想不出来建议直接看我的解析
首先看下面的代码:
public int trap(int[] height) {
if(height.length==0) return 0;
int ans=0;
int len=height.length;
int l_max=0,r_max=0;
int left=0,right=len-1;
while (left<=right){
l_max=Math.max(l_max,height[left]);
r_max=Math.max(r_max,height[right]);
left++; right--;
}
}
对于这部分代码,请问l_max和r_max
分别表示什么意义呢?
很容易理解,l_max是[0..left]
中最高柱子的高度,r_max是[right..n-1]
的最高柱子的高度。
明白了这一点,直接看解法:
class Solution {
public int trap(int[] height) {
if(height.length==0) return 0;
int ans=0;
int len=height.length;
int l_max=0,r_max=0;
int left=0,right=len-1;
while (left<=right){
l_max=Math.max(l_max,height[left]);
r_max=Math.max(r_max,height[right]);
if(l_max<r_max){
ans+=(l_max-height[left]);
left++;
}else {
ans+=(r_max-height[right]);
right--;
}
}
return ans;
}
}
和备忘录
解法对比
之前的备忘录解法,l_max[i]和r_max[i]
分别代表height[0..i]和height[i..n-1]
的最高柱子高度。
但是双指针解法中,l_max和r_max
代表的是height[0..left]和height[right..n-1]
的最高柱子高度。
比如这段代码:
if (l_max < r_max) {
res += l_max - height[left];
left++;
}
此时的l_max是left指针左边的最高柱子,但是r_max并不一定是left指针右边最高的柱子,这真的可以得到正确答案吗?
可以,因为我们只在乎min(l_max, r_max)
。
对于上图的情况,我们已经知道l_max < r_max
了,至于这个r_max是不是右边最大的,不重要。重要的是我已经知道了l_max < 右边最大
是一定成立的就行。