原题目出处:11. 盛最多水的容器 - 力扣(LeetCode) (leetcode-cn.com)
分享一个自己想的O(nlgn)的方法。
这道题看到的时候确实想着用双指针来解,包括官方题解和各种其他大佬分享的都是双指针。无奈鄙人太菜在想双指针的时候发现不像其它双指针题目,至少是个排好序的数组用双指针还过得去。不过正如这位老哥吐槽的,确实有点难证明双指针的正确性,对于第一次做这道题来说其实还是比较难想到的。
说说自己的思路:
本质上,咱们其实可以用两个for循环来解决这个问题,外层的for循环是起点,里层的for循环是终点,然后比较看哪边小,再乘以长度即可。于是对于一个确定的终点来说,如果这个终点是数值比较小的那一个的话,重点就是找到一个离这个终点最远而且比它大的数作为起点
比如对于输入[1,8,6,2,5,4,8,3,7], 第2个数8作为终点时在它前面找不到比它大的数,但是对于第3个数6,比它大且离它最远的就是8。同理,对于最后一个7,离它最远且比它大的是第二个8,而不是倒数第三个8.
所以问题可以归结为:对于一个数,如何找到一个在它前面的,离它最远且比它大的数。
于是我借鉴了“单调栈”的思想,用一个list来维持单调递增的序列。
循环遍历数组,如果当前数比栈顶大,则直接入栈。如果比栈顶小,就去栈里面找第一个大于等于这个数的值。这个值就是我们上面说的在它前面的,离它最远且比它大的数。
算法的正确性证明如下:
首先,按照我们的算法。这个栈是一个单调上升的序列,所以肯定有当前遍历过的数的最大值在栈中。那么,会不会存在一个数不在栈中但比当前数大呢?
比如假设栈里是1,3,8。 如果这时后面的两个数是6, 2。对于6,比它大的是栈中的8。对于2,虽然6也比它大,但是6并不在栈中(因为6比8小,不会进栈)。但是,其实我们并不用关注6。因为栈里的肯定比这个数大而且比它来的早。比如这里的8就是比6大并且离2更远,所以直接忽略6就行。
得证。
在实现的过程中,我们可以用list来表示这个"栈",然后因为list中的数是单调递升的,所以可以用二分法来求第一个大于等于当前遍历到的数的值
满心欢喜提交算法的第一版:
public int maxArea(int[] height) {
List<Integer> stack = new ArrayList<>();
int result = Integer.MIN_VALUE;
for (int i = 0; i < height.length; i++) {
if (stack.size() == 0 || height[i] > height[stack.get(stack.size() - 1)]) {
stack.add(i);
} else {
int left = 0, right = stack.size() - 1;
while (left < right) {
int mid = (right - left) / 2 + left;
if (height[stack.get(mid)] < height[i]) {
left = mid + 1;
} else {
right = mid;
}
}
result = Math.max(result, (i - stack.get(left)) * height[i] );
}
}
return result;
}
怒斥一发error。
原因对于单调递升的数组没有考虑到。比如对于输入[1,2,3,4,5]算出来就是Integer.MIN_VALUE
我想了下,只是把原来的数组翻转一遍再调用一次比较。感兴趣的同学也可以帮忙优化下。
最后提交的代码:
public int maxArea(int[] height) {
int result = solve(height);
List<Integer> list = Arrays.stream(height).boxed().collect(Collectors.toList());
Collections.reverse(list);
int[] height_ = list.stream().mapToInt(i -> i.intValue()).toArray();
result = Math.max(result, solve(height_));
return result;
}
private int solve(int[] height) {
List<Integer> stack = new ArrayList<>();
int result = Integer.MIN_VALUE;
for (int i = 0; i < height.length; i++) {
if (stack.size() == 0 || height[i] > height[stack.get(stack.size() - 1)]) {
stack.add(i);
} else {
int left = 0, right = stack.size() - 1;
while (left < right) {
int mid = (right - left) / 2 + left;
if (height[stack.get(mid)] < height[i]) {
left = mid + 1;
} else {
right = mid;
}
}
result = Math.max(result, (i - stack.get(left)) * height[i]);
}
}
return result;
}
效果当然是比不上O(n)的...