Container With Most Water【中】

探讨了如何找到由非负整数数组定义的垂直线中能构成的最大矩形面积。介绍了两种算法,一种是优化后的O(n²)解法,另一种是更高效的O(n)解法,并详细解释了背后的逻辑。

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

Container With Most Water

题目意思:给定一个非负的整数数组,a1,a2,a3,a4.....,an,其中每一个数都代表一个坐标为(i,ai)的点。现在每个点垂直与x轴相交,交点为(i,0),这样就构成了n条垂直线。现在从n条线里面挑出两条,然后和x轴围城了一个矩形问围成的最大的矩形的面积是多少。

难度:中等

此题很容易想到On^2)的解法,围成的所有可能的矩形数量为n^2个,所以从这些矩形里面挑出面积最大的即可。所以算法复杂度为On^2)。然而如果这样解,就远远超出了限制的时间范围了,这道题只有On)的时间复杂的才能Accept。这里介绍两种算法,一种是优化了的On^2)算法,一种是On)算法

1、 On^2)解法

不难想到对于每一条直线而言,它所能围成的最大面积是和离他最远的一条长度不短于它自身的直线围成的面积取的最大,下面给与证明过程:

I.对于最短一条直线i,它所能围成的最大面积为Si=max|x-i|*ai (ax >= ai),当|x-i|取得最大值时S有最大值

Ii. 对于第二短的直线j,那麼它所能围成的面积为Sj=max{max|x-j|*aj, |i-j|*ai} (ax>= aj && aj >= ai),即在它自身和比它长的直线取得最大值和与它短的直线围成的面积两者之间取最大

Iii.对于第三短的直线k,同理Sk=max{max|x-k|*ak, |k-j|*aj,|k-i|*ai} (ai<=aj<=ak=ax)

以次类推,对于任意一条直线Kn,SKn=max{max|x-Kn|*aKn, |Kn-Kn-1|*aKn-1, |Kn-Kn-2|*aKn-2, |Kn-Kn-3|*aKn-3.....,|Kn-K1|*aK1}

由于,max|x-Ki|*aKi >= |Kn-Ki|*aKi 所以Ski >= |Kn-Ki|*aKi

所以由于在已经计算了SK1SKn-1,计算SKn时只需要计算max|x-Kn|*aKn,最大值只可能在{SK1,SK2,SK3,SK4....,SKn-1,max|x-Kn|*aKn}某一项取得最大值,即只需要计算aKn和距离它最远的不短于它的直线所围成的面积即可。下面是代码:

class Solution {
public:
    int maxArea(vector<int>& height) {
        int isize = height.size();
        
        //std::cout<<isize<<std::endl;
        
        int i, l, r, iResult = -1;
        int iTemp = 0;
        int w, h;
        for(i = 1; i <= isize; i ++)
        {
            l = 1;
            r = isize;
            
            w = (i - 1) > (isize - i) ? (i - 1) : (isize - i);
            if(height[i - 1] * w <= iResult)//优化1
                continue;
            while(1)
            {
                if(l < i && l >= 1 && height[l - 1] < height[i - 1])
                {
                    l ++;
                }
                else
                {
                    break;
                }
            }
            while(1)
            {
                if(r > i && r <= isize && height[r - 1] < height[i - 1])
                {
                    r --;
                    if(r - i <= i - l)//优化2
                    {
                        r = i;
                        break;
                    }
                }
                else
                {
                    break;
                }
            }
            if(l == i && r == i)
                continue;
            if(l == i)
                w = r - i;
            else if(r == i)
            {
                w = i - l;
            }
            else w = r - i;
            
            iTemp = w * height[i - 1];
            iResult = iResult > iTemp ? iResult : iTemp;
            //std::cout<<iResult<<" "<<i<<std::endl;
        }
        
        return iResult;
    }
};


 

对于15000个数据的测试用例需要运行500ms,所以费了老劲优化最终还是会超时==

下面介绍一种On)的解法

 

2、 On

 

这道题可以从两边开始逐渐向中间靠近的方法计算最大值,也就是所谓的两点法:维护两个变量l,r分别从左右两边开始逐渐向中间靠拢,当height[l] < height[r] l++,如果height[l]==height[r] l++,r--;如果height[l] > height[r]r--通俗的概括谁小谁向中间靠拢即Slr=(r - l)*min{height[l],height[r]},遍历取其中最大值即可所以算法复杂度为On^2),下面给出这种解法的正确性的证明:

对于任意的两条直线i,j (j > i)有三种情况ai < aj, ai=aj, ai>aj

(1)ai < aj

 

在这个前提下按照算法规则i++,j保持不变。面积Sij=(j - i) * ai,即对于直线i只需要计算其与aj的所围成的面积即可,而不需要枚举它和每条先围成的面积。你也许有疑问:也许存在一条直线kk>j,Sik围成的面积才是直线i所能围成的最大面积,也就是说不应该只计算了Sij就把i++。假设存在直线k使得Siki直线所能围成的最大面积,那麼根据算法规则在直线左边肯定存在一条直线x,满足ax>akx<=i,那麼一定有Sxk =k-x*ak >= Sik=(k-i)*min{ai,ak} 因为(k-x) >= (k-i)ak>=min{ai, ak}。根据规则SxkSij计算之前一定已经计算过了,所以也就没必要再枚举i,k的情况故而i++没有错。

(2) 同理可以证明ai==aj以及aj < ai的情况,都只需要计算当前Sij即可。

下面是代码:

class Solution {
public:
    int maxArea(vector<int>& height) {
        int isize = height.size();
        
        //std::cout<<isize<<std::endl;
        
        int i, l, r, iResult = -1;
        int w, h;
        l = 0;
        r = isize - 1;
        //从两边向中间靠拢
        while(l < r)
        {
            h = height[l] < height[r] ? height[l] : height[r];
            iResult = iResult > (r - l) * h ? iResult : (r - l) * h;//计算出较短的直线所能围成的最大面积
            if(height[l] < height[r])
                l ++;
            else if(height[l] > height[r])
                r --;
            else
            {
                l++;
                r --;
            }
        }
    
        return iResult;
    }
};


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值