牛客网第一课的第二题
问题:.给定一个非负数的数组,代表一个容器。例如数组[0,1,0,2,1,0,1,3,2,1,2,1],就是以下图形中黑色的部分。如果用这个容器接水的话,请问可以接多少水?还以这个数组为例, 可以接6格水,就是以下图形中蓝色的部分。 要求:实现时间复杂度O(N),额外空间复杂度O(1)的解法。
比较朴素的想法就是我们把数组中的数当作容器壁,求出各个容器壁之间可以存多少水,就可以得到总量了。实现起来不是很容易。这里提出求每个容器壁上面可以存多少水,再将所有的加在一起,得出结果。按照这个思路我们有了以下几种解法。
解法1:求出数组当前位置左右两边的最大值,选择小的那个减去当前位置的值,这个结果就是当前位置的存水量。这个是我们最直接的想法,遍历时,我们要遍历当前位置的左右,其时间复杂度无疑是O(N*N)的。
解法2:基于解法1,我们可以先将当前位置的左右最大值各保存到一个辅助数组中,遍历时直接在辅助数组中取,时间复杂度是O(N)级别的,空间复杂度亦为O(N)级别,在这里我们可以只开一个辅助数组,保存右边的最大值,我们从左遍历时可以得到当前位置的左边最大值,因为我们是从左到右遍历的。
解法3:这里推荐这个解法,比较巧妙,而且复杂度符合题意。我们从数组两段开始遍历,左指针LMAX保存左边当前最大值,右指针RMAX保存右边当前最大值,T=min(LMAX,RMAX),我们可以确定T个位置左边或右边(LMAX和RMAX中间的位置)的存水量。用T减去该位置的值就是该位置的存水量。虽然我们只找到该位置一边的最大值,就是T,但是我们已经找到这个位置的瓶颈了,因为另一边(LMAX或者RMAX)虽然不是最大值,已经比T大了,所以T就是这个位置的短板。接下来T位置向中间滑动一个位置,并更新当前位置最大值。重复上述操作,知道LMAX和RMAX相遇,算法终止,我们只遍历了一遍,时间复杂度O(N),额外空间复杂度,LMAX和RMAX,O(1)级别。
public class LeetCode42 {
public int trap(int height[]) {
if (height == null || height.length < 3) {
return 0;
}
int n = height.length;
int LMAX = height[0];// 左边最大值
int RMAX = height[n - 1];// 右边最大值
int sum = 0;
int L = 1;
int R = n - 2;
while (L <= R) {
if (LMAX <= RMAX) {
sum += Math.max(LMAX - height[L], 0);
LMAX = Math.max(LMAX, height[L++]);
} else {
sum += Math.max(RMAX - height[R], 0);
RMAX = Math.max(RMAX, height[R--]);
}
}
return sum;
}
// public static void main(String[] args) {
// int[] a = { 0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1 };
// System.out.println(trap(a));
// }
}
在这里,我们有了一个双 指针的玩法:两个指针产生的结果会决定下一步哪个指针动。