一步步改进Container With Most Water解法

本文针对给定坐标上的垂直线段,探讨如何选取两条线段与x轴形成最大容积的容器。通过三种不同的算法思路——简单遍历法、圆规法及端点法,详细分析了每种方法的实现过程及其优劣。

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

题目描述

Given n non-negative integers a1a2, ..., an , where each represents a point at coordinate (iai). n vertical lines are drawn such that the two endpoints of line i is at (iai) 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 and n is at least 2.

题意分析

从上面这张示意图当中可以看出来,这道题从直观上来看就是“木桶原理”(一只木桶盛水的多少,并不取决于桶壁上最高的那块木块,而恰恰取决于桶壁上最短的那块)的变体。为了想象得更生动些,我们可以把图上那两根红柱子分别围绕着它们连线的中点旋转180°,获得两个半圆柱体,再将这两个圆柱体拼合成为一个水桶。水桶的容量等于底面积乘高,这里我们用两根柱子之间的距离代替底面积,而高度根据木桶原理就是较矮的那根柱子的高度。

于是这道题转化为如下描述:

在给定的一组数中找出这样两个数,使得它们之间较小的那个数与它们出现次序之差的乘积最大,并返回这个乘积。而在木桶效应中,短板是最重要的,在固定一个点作为木桶的短板之后,剩下我们需要找的,就是距离这个点最远的比它要大的点。

思路一:简单粗暴的遍历法

  • 依次将每一个点作为短板
  • 再次遍历寻找另一个目标点
  • 计算当前围成水桶的容量
  • 与记录的最大值比较,若超过则更新之

 

思路二:左右腾挪的圆规法

为了加速算法,一个很容易想到的办法就是分别从第一个元素往后以及从最后一个元素往前查找第一个比短板要长的元素,这样就可以得到在两个方向上距离短板点最远的长板。为了能在一个方向的遍历中利用另一个方向遍历的结果,特更新出圆规法如下。(自己瞎起的名字)

  • 首先将‘圆规’的支点立于短板点
  • 用笔尖在从距离短板点较远的一侧(比如说短板点在开头的话,就是从最后一个元素那里)到短板点之间的第一个大于短板点的点上做一个标记
  • 将圆规掉转180度,在反方向做一个同样的标记
  • 从另一侧到刚刚做的标记之间寻找比短板点更大的点,如果找到,新木桶的直径就是这一点到短板点之间的距离,否则,就是圆规两脚之间的距离
  • 计算当前围成水桶的容量
  • 与记录的最大值比较,若超过则更新之
class Solution {
public:
    int maxArea(vector<int>& height) {
        int max = 0;
        int l = height.size();
        int radius = 0;
        int n_area = 0;
        //计算前半段
        for (int i = 0; i < l/2; i++) {
        	if (height[i]*(l-i) < max) {
        		continue;
        	}
        	radius = 0;
        	for (int j = l-1; j > i; j--) {
        		if (height[j] >= height[i]) {
        			radius = j-i;
        			n_area = radius*height[i];
        			break;
        		}
        	}
        	for (int j = 0; j < i-radius; j++) {
        		if (height[j] >= height[i]) {
        			n_area = (i-j)*height[i];
        			break;
        		}
        	}
        	max = max > n_area ? max : n_area;
        }
        //计算后半段
        for (int i = l/2; i < l; i++) {
        	if (height[i]*i < max) {
        		continue;
        	}
        	radius = 0;
        	for (int j = 0; j < i; j++) {
        		if (height[j] >= height[i]) {
        			radius = i-j;
        			n_area = radius*height[i];
        			break;
        		}
        	}
        	for (int j = l-1; j > i+radius; j--) {
        		if (height[j] >= height[i]) {
        			n_area = (j-i)*height[i];
        			break;
        		}
        	}
        	max = max > n_area ? max : n_area;
        }
        return max;
    }
};

 

虽然过是过了,但运行时间好像不是很快,会不会有更好的方案呢?

 

思路三:来回变换的端点法

刚刚说的两个思路其实都有局限,就是每次都要指定一个点为短桶点,寻找另一个长桶点。其实可以从两端开始选点,分别向中间运动,短桶点和长桶点在两个端点之间来回变换。

  • 将两个端点分别选为起始点
  • 计算两个端点围成的体积
  • 根据结果更新最大体积
  • 两个端点中的短桶点向中心移动一步
  • 重复上述步骤直至两个端点相遇
class Solution {
public:
    int maxArea(vector<int>& height) {
        int max = 0, n_area = 0, start = 0, end = height.size()-1, hs = 0, he = 0;
        while (start != end) {
        	hs = height[start], he = height[end], n_area = end-start;
            if (hs > he) {
                n_area *= he;
                end--;
            }
            else {
                n_area *= hs;
                start++;
            }
            max = max > n_area ? max : n_area;
        }
        return max;
    }
};

可以看到速度提升了许多。

注意事项

短桶点未必小于长桶点,判断时要注意包含两者相等的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

羊城迷鹿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值