LeetCode:Container With Most Water

本文针对给定坐标上的垂直线段,探讨如何选取两条线段与x轴形成能容纳最多水的容器。提供了四种不同复杂度的解决方案,包括暴力枚举、基于排序的方法、逐步缩小范围的策略以及高效的双指针技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/**
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.

题目大意:二维坐标轴上有n个点[i, ai],每个点画一条x轴的垂线(连接[i, ai]和[i, 0]),选两条线作为盆子装水,找出最多水的方案
**/
import java.util.*;

public class Solution {
    public int maxArea(int[] height) {
        //return solve1(height);      //超时
        //return solve2(height);      //想法错误
        //return solve3(height);
        return solve4(height);
    }
    
    //================================ solve1 ========================================
    //O(n^2)
    //双循环直接穷举,必然超时
    private int solve1(int[] height){
        int result = -1;
        int index = -1;
        
        for(int i=0; i<height.length; i++){
            for(int j=i+1; j<height.length; j++){
                index = i;
                if(height[i] > height[j])
                    index = j;
                
                int water = height[index] * (j-i);
                if(result < water)
                    result = water;
            }
        }
        
        return result;
    }
    
    //================================ solve2 ========================================
    //想法本身有问题,没能维护两点之间的宽度,算出来的果断是错误答案
    //O(n * log2 n) = O(n * log2 n) + O(n)
    //先对数组进行排序,然后遍历,对于[i, ai],直接乘以他到最优的距离就可以得出当前点可容纳最多水的数量
    private int solve2(int[] height){
        Arrays.sort(height);
        
        int result = -1;
        int tmp = -1;
        
        for(int i=0; i<height.length; i++){
            tmp = height[i] * (height.length - 1 - i);  //注意这里要减1才能得到宽度
            
            if(result < tmp)
                result = tmp;
        }
        return result;
    }
    
    //================================ solve3 ========================================
    //O(m^2) = O(n) + O(n) + O(m^2)
    //搜索左端点 + 搜索右端点 + 查询并获取最大值
    /**
    考虑从这个题目本身的数学性质出发:
        现在指定两点[i, ai],[j, aj],面积f(1)=(j-i)*min(ai, aj),固定左端点,移动右端点:
            1、右端点右移,有两种情况:
                [j+k, b1],其中b1>=aj,则f(b1)=(j+k-i)*min(ai, b1),明显可得f(b1)>f(1);
                [j+k, b2],其中b2<=aj,则f(b2)=(j+k-i)*min(ai, b2),f(b2)和f(1)大小不定;
            2、右端点左移,也有两种情况:
                [j-k, c1],其中c1>=aj,则f(b1)=(j-k-i)*min(ai, c1),f(c1)和f(1)大小不定;
                [j-k, c2],其中c2<=aj,则f(b2)=(j-k-i)*min(ai, c2),明显可得f(c2)<f(1);
        
        左端点的情况和右端点对称,可以得出结论:如果程序从两端开始,向中间收缩,只能选择越来越高的点才能保证容量不变小。
    
    例如,序列是[5(0),4(1),6(2),12(3),8(4),32(5),9(6),1(7),4(8)],
        左端点i的选择只能是:0 -> 2 -> 3 -> 5
        右端点j的选择只能是:8 -> 6 -> 5
        可以从这一点出发,对水盆左右端点的情况进行缩减
    **/
    private int solve3(int[] height){
        int[] leftPoint = new int[height.length];
        int leftNum = 0;
        
        int[] rightPoint = new int[height.length];
        int rightNum = 0;
        
        //选取左端点集合:
        for(int i=0, currentPoint=height[i]; i<height.length; i++){
            if(height[i] >= currentPoint){
                leftPoint[leftNum++] = i;
                currentPoint = height[i];
            }
        }
        
        //选取右端点集合:
        for(int i=height.length-1, currentPoint=height[i]; i>=0; i--){
            if(height[i] >= currentPoint){
                rightPoint[rightNum++] = i;
                currentPoint = height[i];
            }
        }
        
        //在所得的点里开始计算
        //这里可以有一个优化的策略:当一端静止,另一端向其靠近,如果另一端的高度比这一端要高,那就可以结束当前循环了
        int result = -1;
        for(int i=0; i<leftNum; i++){
            for(int j=0; j<rightNum; j++){    //在这里可以加一个条件
                int left = leftPoint[i];
                int right = rightPoint[j];
                if(left >= right)
                    break;
                int width = right - left;
                
                //这里要用矮的那一个作为盆子的高度
                int tall = min(height[left], height[right]);
                
                int water = tall * width;
                if(result < water)
                    result = water;
            }
        }
        
        return result;
    }
    
    //================================ solve4 ========================================
    //O(n)
    //左右靠拢,到中间合并的时候必然可得最大值
    /**
    现在试试左右两端往中间收窄的策略:
        当固定其中一端i,另一端j向其靠近,如果发现 height[j] >= height[i];
        那么这个时候j就不需要在靠近了,因为即使在靠近,高度也限制在i点上,无法增加,
        此时应固定j点,转由i点向j点靠近。
        同时对中间每一步产生的容量值进行计算,得出最大值。
        
    其实使用了solve3的一些结论,在这里只移动矮的那一边,因为移动高的那一边没有意义
    计算完毕之后一定能得到最大值
    **/
    private int solve4(int[] height){
        int left = 0;
        int right = height.length - 1;
        
        int result = -1;
        
        while(left < right){
            int tmp = (right - left) * min(height[left], height[right]);
            
            result = max(result, tmp);
            
            if(height[left] <= height[right])
                left++;
            else
                right--;
        }
        return result;
    }
    
    private int min(int a, int b){
        if(a < b)
            return a;
        else
            return b;
    }
    
    private int max(int a, int b){
        if(a < b)
            return b;
        else
            return a;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值