leetcode [array] No.11 Container With Most Water

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Richard_M_pu/article/details/82559838

题目描述

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 and n is at least 2.
这里写图片描述
Example

Input: [1,8,6,2,5,4,8,3,7]
Output: 49

解题思路

1. 思路一:

最开始也最简单的一个思路是:从 0 n 1 ,依次选择每个点作为左端点,当选择第 i 个点作为左端点时,从 i + 1 n ,依次选择每个点作为右端点,这样便是遍历了所有的情况,计算每次可容纳的水的体积,返回最大的值。

代码

class Solution {
public:
    int maxArea(vector<int>& height) {
        int size = height.size();
        int max = 0, container = 0;
        for (int i = 0; i < size - 1; i++) {
            for (int j = i + 1; j < size; j++) {
                container = min(height[i], height[j]) * (j - i);
                if (container > max) {
                    max = container;
                }
            }
        }
        return max;
    }

private:
    int min(int a, int b) {
        return (a < b ? a : b);
    }
};

运行结果
运行结果1
显而易见,上述算法思路的运行效率是 Θ ( n 2 ) ,从与其他人的效率对比上也可以看出,这种算法的效率不算是很高。

2. 思路二:

经过一些研究我们可以发现,若左端点已经固定为点 i ,那么对于任意右端点 j j < k < n a k > a j ,那么选 k 作为右端点时的容量一定比选 j 作为右端点大。直观上来说,就是如果在某个“木板”的右侧,有一块更长的“木板”,那么选择右边的这块“木板”,不仅可以增加“水桶”的宽度,还可能增加“水桶”的高度,所以“水桶”的容量一定增加。

那么我们在遍历所有情况之前,可以先遍历一遍所有“木板”的高度,做一遍预处理。我们新建一个数组back,用于记录当前节点右侧(不包括当前节点)最长的“木板”的坐标(即最大的 a i 的下标 i )。然后从右往左遍历“木板”的高度,对于节点 i ,若 a i + 1 > a b a c k [ i + 1 ] ,则 b a c k [ i ] 记为 i + 1 ,否则 b a c k [ i ] ,记为 b a c k [ i + 1 ]

经过预处理后,我们可以依然从 0 n 1 ,依次选每个节点当做左端点,而右端点的选择则不再需要遍历左端点右侧的所有点,只需依次选择当前节点右侧最大的点即可。

代码:

class Solution {
public:
    int maxArea(vector<int>& height) {
        int size = height.size();
        int max = 0, container = 0;
        int back[size] = {};

        back[size-1] = size - 1;
        for (int i = size - 2; i >= 0; i--) {
            if (height[i+1] > height[back[i+1]]) {
                back[i] = i + 1;
            } else {
                back[i] = back[i+1];
            }
        }

        for (int i = 0; i < size - 1; i++) {
            int j = 0;
            do {
                j = back[j];
                container = min(height[i], height[j]) * (j - i);
                if (container > max) {
                    max = container;
                }               
            } while(back[j] != j);
        }
        return max;
    }

private:
    int min(int a, int b) {
        return (a < b ? a : b);
    }
};

运行结果:
这里写图片描述
这种算法的预处理效率是 Θ ( n ) ,而之后的运算没有遍历所有的可能性,所以效率比思路一中的算法要高,但是实际的运行结果显示这种算法的运行更慢了,猜想可能是测试用的测试集 n 较小,而做预处理的时间比做完预处理节省的时间还要长。

扫描二维码关注公众号,回复: 3745303 查看本文章

3. 思路三:

在思路二的基础上,我们再进行一些改进,既然在右端点的选择上,我们可以“剪枝”,去掉一些不可能的情况,那么在左端点的选择上,我们应该也能进行“剪枝”,类似于思路二中的想法,如果左端点的左边有一块“木板”比它长,那么选择左边那块木板一定可以使得“木桶”的容量变大。

所以再建立一个数组 f o r w a r d ,用于记录当前节点左侧最长的“木板”的下标。建立方法类似于 b a c k ,只需遍历一遍记录“木板”高度的数组即可。

在预处理完之后,我们得到了两个数组 f o r w a r d b a c k ,我们先选择最右侧的点作为左端点,然后依次选择当前节点左侧最大的点作为左端点,对于已经选定好的左端点,和思路二中的算法一样,依次选定右侧最大的点作为右端点即可。

代码:

class Solution {
public:
    int maxArea(vector<int>& height) {
        int size = height.size();
        int max = 0, container = 0;
        int forward[size] = {}, back[size] = {};

        forward[0] = 0;
        for(int i = 1; i < size; i++) {
            if (height[i-1] > height[forward[i-1]]) {
                forward[i] = i-1;
            } else {
                forward[i] = forward[i-1];
            }
        }

        back[size-1] = size - 1;
        for (int i = size - 2; i >= 0; i--) {
            if (height[i+1] > height[back[i+1]]) {
                back[i] = i + 1;
            } else {
                back[i] = back[i+1];
            }
        }

        int i = size - 1, j = 0;
        do{
            i = forward[i];
            j = i;
            do {
                j = back[j];
                container = min(height[i], height[j]) * (j - i);
                if (container > max) {
                    max = container;
                }               
            } while(back[j] != j);
        } while(forward[i] != i);
        return max;
    }

private:
    int min(int a, int b) {
        return (a < b ? a : b);
    }
};

运行结果:
这里写图片描述
可以看出,思路三的算法效率有很大的提升,两次预处理的效率均为 Θ ( n ) ,之后计算结果时,因“剪”掉了大量不可能的结果,效率有了很大的提升,感觉上应该接近 O ( n )

猜你喜欢

转载自blog.csdn.net/Richard_M_pu/article/details/82559838