LeetCode 11_Container With Most Water

今天来写leetcode第11题,这个题目是一道“应用题”,不过并不存在理解题意方面的问题。题目确实是考察算法的,这么说是因为解题的思路有很多种,而其中有一个最简单的,不过这可不是轻轻松松就能找到的。

原题如下:

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.

题目的意思大概是这样的,给定一个非负整数数组,然后把某个数所在位置(数组下标)作为横坐标而数字本身作为纵坐标,这样在x轴上方会画出很多个点,再通过这些点做x轴的垂线,最后就得到了很多垂直的线,把这些线看做容器壁,x轴看做容器底,问哪条线和x轴构成的容器称的水最多,求出这个最大的容量。

这个题的解法可以说是“飘在水面”上的,显然可以通过蛮力法来解决这个问题,即每两个线都组合试一下,看哪个最大。这样一来会形成时间复杂度为n(n-1)/2(即O(n^2))的算法,这样的算法显然是“太幼稚了”。我们应该试图改进这种算法,或者开辟新的算法,可惜我只是改进了这个蛮力法并没有找到所谓的最优算法,但改进的效果还是可以的,所以还是给大家说一下,稍后再介绍最优算法。

我的思路是这样的,因为数组并没有什么特殊要求,所以假设其为随机数,这样一来那显然会有一部分线是可以提前排除的,也就是说是不可能用到它的。比如,如果一根线左边和右边都有比它长的线,那显然这条线不可能作为最大容器的一部分,因为如果它是最大容器的一部分那很容易证明容器还可以扩容:如果它在水的左边,那可以再扩展到左边那条比它长的线,同理如果它在水的右边可以扩容到右边比它长的线。总之,中间是短线的不可能起作用,这样可以“删除”一部分线。再对剩下的两个组合。

通过代码设计,发现这种算法对应随机输入的数组确实有比较好的效果,因为很多线都被删去了。但对于一些特殊输入,如连续增长的数组,算法效果非常不好,因为算法不能删去任何线反而浪费了时间。以至于当输入较大时算法超时了。

再一次改进还是基于上面的思路,不过这次把左边和右边开了,因为前面的算法实际剩下的是可以做左线或者可以做右线的线,我们还可以将其分开,即那些是只能做左线的,那些是只能做右线的。这么说可能有点儿问题,其实我们找到的是那些“也许会在最后结果里做左线的线”和“也许会在最后结果里做右线的线”。代码如下

class Solution {
public:
    int maxArea(vector<int>& height) {
		vector<int> lefInAction;
		int len=height.size();
		int max=0;
		for(int i=0;i<len;++i)
		{
			if(height[i]>max)
			{
				max=height[i];
				lefInAction.push_back(i);
			}
		}
		max=0;
		vector<int> rigInAction;
		for(int i=len-1;i>=0;--i)
		{
			if(height[i]>max)
			{
				max=height[i];
				rigInAction.push_back(i);
			}
		}

		int lenLefAct=lefInAction.size();
		int lenRigAct=rigInAction.size();

		int maxCap=0;
		int cap=0;
		for(int i=0;i<lenLefAct;++i)
		{
			for(int j=0;j<lenRigAct;++j)
			{
				cap=(rigInAction[j]-lefInAction[i])*min(height[lefInAction[i]],height[rigInAction[j]]);
				if(cap>maxCap)maxCap=cap;
			}
		}
		
};
这样一来代码对特殊输入的改进也是比较大的了,所以程序通过了验证,时间为28ms。这个时间所含信息量比较多,我们一会儿再说。

下面说大神的思路,也就是本题的最优算法。我们这次先上代码(在这里感谢StefanPochmann大神的分享)

class Solution {
public:

    int maxArea(vector<int>& height) {
    int water = 0;
    int i = 0, j = height.size() - 1;
    while (i < j) {
        int h = min(height[i], height[j]);
        water = max(water, (j - i) * h);
        while (height[i] <= h && i < j) i++;
        while (height[j] <= h && i < j) j--;
    }
    return water;   
    }
};
我想这个代码以及所利用的算法并不是很难理解,反正我看了以后有一种恍然大悟的感觉,或许这就是算法的魅力吧。

为了完整性以及不同读者的水平,我们还是来对其做个说明吧:显然其算法的时间复杂度为O(n),所以这应该是最优算法了吧,不过这个问题我不会证明,抱歉,反正直观上理解这个算法是非常“好”的。两个指针分别指向数组头和数组尾,然后比较哪个线比较短,把比较短的往中间移动直到遇到一个比其长的线,在这个过程中跟新容量的最大值。就是这么简单,要想证明这个算正确,我们只需证明“移动所错过的组合中不可能有最大容量”,比如就第一步移动吧,加入左边的比较短,那么左边会移到第二个位置,那么这样会错过所有第一个和除最后一个外其他的线的组合。也就是说你并没有判断第一个和第二是多少,和第三个是多少。。。那么这些错过的里面会有最大容量吗?显然不会,因为左边这个是比较短的,也就是说容器的深度是由它限制的,它和除最后一个外其他线组合时,水的深度不可能超过它,而容器底却一定会缩小,所以这个容器的容量一定会缩小的。其他的情况和此类似。

大神的代码运行时间为24ms,这一点可能有些意外,不过更意外的是统计数据显示这已经是最快的了(没有少于24ms的了),但同时又显示仅仅击败了百分之60多的C++代码,这个不知道什么原因,或许是统计问题吧。至于为什么只比我的“改进算法”代码只快了4ms我想是因为测试实例较少,算法的优越性没有完全体现出来吧,因为无论从哪种角度来说这个代码都是优于我的代码的。

这一道题难度不大,但不失为一道考察算法的好题,通过蛮力法、改进的蛮力法、最优算法的学习过程,我们对算法本身会有更多的了解,对算法的魅力也会有更多的领略。

最后呢,说几句题外话吧,最近一篇名为《阿里缩招(从3000人大幅砍到400人)降薪,互联网寒冬要来了吗?》的文章挺火的,大家可以百度看一下。从标题可以看出,这篇文章是对互联网行业的未来不看好的,因为搞程序搞算法的嘛,所以大家对这个可能比较感兴趣,本人也是这两年要毕业,所以对这些东西比较关注。至于文章写得有没有道理,是否会对你产生影响,那就看各位对这个事情的看法了。不过最后还是那句话,希望每一个坚持的人都能实现自己的理想。加油吧,筒子们


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值