经典题目:盛最多水的容器 —— 分享一个自己想的方法

651 阅读3分钟

原题目出处:11. 盛最多水的容器 - 力扣(LeetCode) (leetcode-cn.com)

分享一个自己想的O(nlgn)的方法。

这道题看到的时候确实想着用双指针来解,包括官方题解和各种其他大佬分享的都是双指针。无奈鄙人太菜在想双指针的时候发现不像其它双指针题目,至少是个排好序的数组用双指针还过得去。不过正如这位老哥吐槽的,确实有点难证明双指针的正确性,对于第一次做这道题来说其实还是比较难想到的。

image-20220114002206513.png

说说自己的思路:

本质上,咱们其实可以用两个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)的...

image-20220114004509811.png