Description
Given n non-positive 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.
Solution
很容易想到一种暴力直观的解决方法,即二重循环遍历数组,对每一个序对 (i, j) 且 i < j,其组成的容器面积 area = (j - i) * min(height[ai], height[aj])。
在遍历过程中对最大面积进行更新记录即可,整个算法的时间复杂度为O(n²),在leetcode实现并提交后,算法运行时间超出限定值。
这促使我们思考能否提出一种O(nlogn)的算法。
常规的O(nlogn)算法通常为分治,将问题分解为小规模子问题,并通过线性时间对子问题的解进行合并得到最终结果。
然而尝试着将问题划分为两半,在分别得到左右子集的最大容器面积后,很难对线性时间合并给出一个实际而合理的解释。
那能否先对height在O(nlogn)复杂度内进行排序后,通过O(n)得到结果值呢,这样的算法是存在的,其思路大致如下:
重新申请一个结构体,对每个ai的高度与下标值进行记录,因为我希望在排序后,可以检索其原始下标。
对结构体按高度值进行排序后,按从小到大进行遍历。
对于结构体si,其高度值是height数组中最小的,所以如果可以获取height数组中的begin与end位置,结合原始下标即直接可以对其最大可能面积进行计算。
而后删除si,并对begin与end值进行更新。
整个算法结束后可以如期的得到正确的结果,并且将整体算法的复杂度降为O(nlogn)。
如何可以在O(1)的时间内对begin和end的值进行索引并更新呢,如果单纯用数组或者链表存储,在删除原始下标的si后,还需要花费O(n)时间进行移动。
这里我采用的方法是用两个数组来维护类似于双向链表中每个节点的前驱索引值与后驱索引值,是可以在O(1)的时间内完成检索与更新的。
整个算法的实现如下:
struct node {
int height;
int index;
};
bool compare(node left, node right) {
return left.height < right.height;
}
class Solution {
public:
int maxArea(vector<int>& height) {
int size = height.size();
int *forwardVisit = new int[size + 2];
int *backwardVisit = new int[size + 2];
vector<node> heightArray(size);
for (int i = 0; i < size; i++) {
//init heightArray
heightArray[i].height = height[i];
heightArray[i].index = i + 1;
}
//init visit
for (int i = 0; i < size + 2; i++) {
forwardVisit[size] = i + 1;
backwardVisit[size] = i - 1;
}
//排序
sort(heightArray.begin(), heightArray.end(), compare);
int begin = 1;
int end = size;
int result = 0;
for (int i = 0; i < size - 1; i++) {
int pos = heightArray[i].index;
int left_max = pos - begin;
int right_max = end - pos;
int max = left_max > right_max ? left_max : right_max;
int area = max * heightArray[i].height;
if (area > result)
result = area;
if (pos == begin)
begin = forwardVisit[pos];
if (pos == end)
end = backwardVisit[pos];
forwardVisit[backwardVisit[pos]] = forwardVisit[pos];
backwardVisit[forwardVisit[pos]] = backwardVisit[pos];
}
delete[] forwardVisit;
delete[] backwardVisit;
return result;
}
};
可以看到,虽然通过了测试,但是从运行时间与排名来看还存在更好的解决方案,会在以后的时间进行进一步的思考。