leetcode 42 Trapping Rain Water

Trapping Rain Water
Total Accepted: 47928 Total Submissions: 158168 Difficulty: Hard

Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.

For example,
Given [0,1,0,2,1,0,1,3,2,1,2,1], return 6.

The above elevation map is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. Thanks Marcos for contributing this image!

这道题我目前知道两种算法,第一种是我自己想的,效率不高,测试时间是24ms,第二种是别人的做法,效率较高,测试时间是4ms。

首先,记录一下我自己的解法,首先如果一个地方能蓄水那么它的左右两侧必然有比它高的两根杆(姑且称直方图中的竖直矩形为杆),所以我用两个变量firstHeight和secondHeight来记录这两根杆的长度,并且用firstIndex和secondIndex来记录它们对应在数组中的位置。我发现这样的两根杆满足一个条件,就是它们比与它相邻的下一根杆要高,否则,就应该选下一根杆而非这根杆作为蓄水的左右两根杆。这听上去是对的,但是事实却并非完全如此,例如:
这里写图片描述
如果按我们的描述,我们就应该选择用红线圈出来的两根杆,而事实上我们并不是使用右边最高的那根杆作为蓄水的杆,而是使用了它左边的那根杆作为蓄水的杆,这样一来我们之前描述的算法就不能用了,其实只要一点点改进,这个算法照样行得通。

这个改进稍后再讲,现在我们仍然使用原来的算法,那么我们选择的左右两根杆就是图中红线圈出来的杆。它们之间可以蓄水的最大体积,就等于它们两者中较矮的那个的高度乘以两者间的距离,即如图中的绿色部分。
这里写图片描述
显然这个部分比真实的部分大,那么我们就要减去多加的部分,我们就用较小的那根杆的高度去减去,两杆之间的其他每根杆的高度,把这些差相加得到的结果就是多加的高度。但这有个问题,就是但我们有较小的那根杆去减最高的那根杆的左边那根杆时,结果为负数,此时就不能再将结果直接相加了,就要用到我之前说的那个改进,其实很简单,对于这种结果为负的情况,直接忽略,不进行相减操作,其实不难理解,因为在这根杆的位置不会蓄水。经过这样的操作之后,左右两根杆之间蓄水的体积就计算出来了,此时我们就要找到下一组杆,再算它们之间的蓄水体积。那么谁会是下一组杆呢?这会有两种情况:
这里写图片描述这里写图片描述
我们会发现,下一组杆的左杆的选择的实际上是前一组左右杆中的较高的那个。其实到这步之后,我们不断重复上述过程,就可以总的蓄水体积了。但有两点要注意:1. 对于上述的第二种情况,如图红色的部分已经计算过了,如果直接使用上面描述的算法,红色部分就会被再计算一次,为了避免这个错误,我们在第一次计算的过程中,边计算变把坑填平,即如图所示,这样在使用原来算法时,就不会重复计算已经算过的部分了。
这里写图片描述这里写图片描述 2. 我们原始的算法是取每一个比与它相邻的下一根杆要高的杆作为蓄水的两根杆之一来进行计算,但这样实际上产生了很多不必要的计算,如图
这里写图片描述
图中每个红线圈起来的杆都会作为蓄水的杆的右杆然后代入到算法中进行计算,但实际上后面3个杆的计算是不必要的因为第一个杆算完之后就已经填平了,如图,两杆之间部分的高度都高于右杆,因此无法蓄水,所以总的体积不变。
这里写图片描述
因此,我们应该改进这一条件,我们发现真正蓄水的杆不仅是比与它相邻的下一根杆要高,同时比与它相邻的上一根杆也要高,但添加了这一条件后,计算的后3根杆,都不在满足条件,从而不需要考虑了,简化的计算。根据我提交的结果来看,原始算法为32ms,根据这点改进后为24ms。
下面是算法代码:

int trap(int* height, int heightSize) 
{
    int firstHeight = height[0], secondHeight = height[0];
    int firstIndex = 0, secondIndex = 0;
    int sum = 0;

    for(int i = 1; i < heightSize; i++)
    {
        if(height[i] > height[i-1])
        {
            if(i < heightSize - 1 && height[i+1] > height[i])
                continue;

            secondHeight = height[i];
            secondIndex = i;

            int newheight = secondHeight < firstHeight ? secondHeight : firstHeight;
            for(int j = firstIndex+1; j < secondIndex; j++)
            { 
                if(newheight - height[j] > 0)
                {
                    sum += newheight - height[j];
                    height[j] = newheight;
                }
            }

            if(secondHeight >= firstHeight)
            {
                firstHeight = secondHeight;
                firstIndex = secondIndex;
            }
        }
    }
    return sum;
}

另外一种别人的方法更简单,从左往右走,记录当前遇到杆的最高值,如果当前的杆的高度小于最高值,就把最高值与它的差作为蓄水量进行累加,这样累加结果会有一个误差,就是所有根据全部杆最高值进行累计的值,实际上是不存在的(除非最高值在最后一根杆)。如图
这里写图片描述这里写图片描述
如图中绿色部分就是多累加的部分,要扣除这部分也很简单,方法跟前面类似,不过是从后往前走,记录当前遇到的杆的最大值,这个最大值和所有杆的最高杆的高度之差就是我们每根杆要扣除的值,对每根杆计算这个扣除值直到遇到所有杆的最高杆,整个扣除过程结束,此时剩下的累计值就是我们要求的总的蓄水量。

程序完整代码如下:

int trap(int* height, int heightSize) 
{
    int sum = 0;
    int maximum = height[0];
    for(int i = 0; i < heightSize; i++)
    {
        if(maximum < height[i])
            maximum = height[i];

        sum += maximum - height[i];
    }

    int max2 = height[heightSize - 1];
    for(int i = heightSize - 1; i >= 0; i--)
    {
        if(height[i] == maximum)
            break;
        if(max2 < height[i])
            max2 = height[i];
        sum -= maximum - max2;
    }

    return sum;
}

int main()
{
    int heightSize;
    cout<<"Input the size of height:"<<endl;
    cin>>heightSize;
    int *height = (int*)malloc(sizeof(int) * heightSize);
    for(int i = 0; i < heightSize; i++)
    {
        scanf("%d,", &height[i]);
    }

    cout<<trap(height, heightSize)<<endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值