leetcode 11. 盛最多水的容器(Java版)

题目描述(题目难度,中等)

给定 nn 个非负整数 a1a2...ana_1,a_2,...,a_n,每个数代表坐标中的一个点 (i,ai)(i, a_i) 。在坐标内画 nn 条垂直线,垂直线 ii 的两个端点分别为 (i,ai)(i, a_i)(i,0)(i, 0)。找出其中的两条线,使得它们与 xx 轴共同构成的容器可以容纳最多的水。

说明: 你不能倾斜容器,且 nn 的值至少为 2。
leetcode 11. 盛最多水的容器(Java版)
图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

示例:

输入: [1,8,6,2,5,4,8,3,7]
输出: 49

示例代码

时间复杂度为O(n)O(n),空间复杂度为O(1)O(1)

class Solution {
    //这个题放在 在线测试还可以 放在面试中不太好描述吧
    public int maxArea(int[] height) {
    	int i = 0, j = height.length - 1;
    	int mm = 0;//用于存遍历过程中得到的存水最大值
    	while(true){
    		int m = (j-i)*(height[i] <= height[j]?height[i++]:height[j--]);
    		if(m > mm) mm = m;
    		if(i == j) break;
    	}
        return mm;
    }
}

思路解析

上面是一种双指针算法,指针 i 指向数组的开头,满足条件后只向后移动;指针 j 指向数组的末尾,满足条件后只向前移动。两指针相遇,算法结束。能使用这种算法解的题还有很多,在最后我还会举个例子。
那么指针移动的条件是什么呢?
在本题中指针移动的条件是,(首先假设数组为 a)如果 a[i] <= a[j],那么指针 i 则向后移动;如果 a[i] > a[j],那么指针 j 则向前移动。
我们可以发现,虽然 i,j 指针合起来能遍历整个数组,但并没有穷举所有的容器情况。 例如,如果 a[i] <= a[j],则 i++(移动 j 的情况同理证得),排除了 a[i] 与 a[i+1,…,j-1] 之间的某个数构成最大盛水容器的可能性。 为什么可以这样做呢?下面给出证明:
假设存在 k,k 满足 i+1 <= k <= j-1,a[i] 与 a[k] 构成了最大盛水容器,容量为 C。

  1. 如果 a[i] <= a[k],则 C = a[i]*(k - i),那么我们现在来看一下,a[i] 和 a[j] 构成的盛水容器,容量如何,其容量为 a[i]*(j - i) > C,此时假设不成立!
  2. 同理,如果 a[i] > a[k],则 C = a[k]*(k - i),注意此时,由不等式的传递性可得a[j] 和 a[k] 的大小关系为 a[j] > a[k]。所以依然有 a[i] 和 a[j] 构成的盛水容器,容量为 a[i]*(j - i) > C,此时假设依然不成立!

综上可得,a[i] 不可能与 a[i+1,…,j-1] 之间的某个数构成最大盛水容器。证明结束!

最后再给出一道双指针例题:
给一个按升序排序的整数数组,寻找数组中和等于某个特定值的两个数,返回这两个数的下标。
示例的 Java 代码如下:

public static int[] twoSum(int[] a, int target){
	int i = 0, j = a.length - 1;
	while(true){
		if(target == a[i] + a[j]) return new int[]{i, j};
		if(a[i] + a[j] < target) ++i;
		else --j;
		if(i == j) return null;
	}
}