缘起
最近在牛客网上看到一个容器盛水问题挺有意思,更有意思的是我还想了一天,然后再下班的地铁上顿悟了···
这个算法题让我想起了读书时一位老师跟我说过的一句话:一个木桶能装多少水不是由最长的那根木板决定,而是由最短的那根木板决定。
这道算法题的解法就根据这句话求解。
思路
小编使用双指针的思路进行解题。
举个例子,假设给定的数组为:[4,3,3,5,1,3,2,3],我们根据这个数组画出对应的图:
其中,粉红色(是粉红色吧?)的方块就是可以盛水的位置,总的盛水量为所有粉色方块的总和。
- 我们定义两个指针,分别为
left
和right
,left
指针开始的位置为数组的第一个元素下标,right
开始的位置为数组的最后一个元素的下标。 - 从两边开始向中间缩进。这里有个点,目前来看,我们已知的就两个指针对应下标数组的值,其他的都不知道,那么,我们可以假装看不见其他数据,此时画出的图为:
顿时,数组看起来就剩下这两个元素了。 - 开始向中间缩进(现在就两块木板,想装水也装不了呀,只能开始缩进),先确定两块木板中,最短的那一块,也就是3,即
right
指针向左移动。
此时,2已经出来了,现在为[4,2,3],能装的水为3-2=1,即右边最大的木板减去当前的木板长度。 - 此时的
right
已经到了2的下标位置,由于现在left
指针对应的值还是比right
指针对应的值大,所以还是right
指针往左移动。此时3出来了,现在为[4,3,2,3],能装的水从图就可以看出,那有什么规律吗?是不是右边最大的木板减去当前的木板长度?右边最大的木板为3,当前木板为3,再加上之前已经装水的量,所以当前能装水的量还是为1。
5.重复上面的步骤,right
指针还是小于left
指针,还是right
指针向左移动。
现在再算一下当前盛水的总量=之前的盛水量+(右边最大的木板长度-当前的木板长度),这一算就是3,符合。
6. right
指针还是小于left
指针,还是right
指针向左移动。
此时的right
指针已经到了5的下标位置,那之前的公式是否还成立?答案是成立的,因为现在右边最大的木板已经变成了5,当前的木板也是5,最大的木板减去当前的木板为0,对结果并没有影响。
7. 现在right
指针对应的值已经是5了,而left
的指针对应的值为4,遵循移动小木板的原则,接下来应该移动left
指针向右移动了。
此时数组为[4,3,3,5,1,3,2,3],按照之前的做法,用左边最大的木板减去当前的木板得到当前盛水量(哪一边移动,就使用哪一边的最长木板来进行计算,因为移动的永远都是短木板,在短木板的那一边,只需要找到这一边最大的木板,就能保证短木板一边的木板长度不会超出当前最短的木板)
8. 此时left
指针的值为3,right
指针的值为5,所以left
指针向右移动
依照上面的计算方法,计算出当前的盛水量
9. 依据上面的最小移动原则,此时left
和right
两个指针的下标已经重合了,也就说明已经遍历完了整个数组,该退出循环了。
发现规律
从上面的解题思路来看,我们使用了双指针,哪一边的指针小,就移动哪一边的指针,移动之后,我们需要确定这一边的最大的值,然后套用最大值-当前值
的公式就能得出当前的盛水量,将所有的盛水量加起来就是总的盛水量
代码
package com.mywork.niuke;
/**
* 容器盛水问题
*
* 给定一个整形数组arr,已知其中所有的值都是非负的,将这个数组看作一个容器,请返回容器能装多少水。
*
*/
public class FindMaxWater {
public static void main(String[] args) {
// int [] arr = {4,3,3,5,1,3,2,3};
int [] arr = {10,1,10};
System.out.println(maxWater(arr));
}
public static long maxWater (int[] arr) {
int left = 0; //左指针
int right = arr.length-1; //右指针
int leftMax = arr[0]; //初始左边最大值
int rightMax = arr[arr.length-1]; //初始右边最大值
long result = 0l;
while (left < right){
if (arr[left] < arr[right]){
//左指针移动
++left;
//确定左边最大值(从端点开始,找最大值无非就是比较当前和保存起来的最大值
if (leftMax<arr[left]){
leftMax = arr[left];
}
result += (leftMax - arr[left]);
}else {
//右指针移动
--right;
//确定右边最大值
if (rightMax<arr[right]){
rightMax = arr[right];
}
result += (rightMax - arr[right]);
}
}
return result;
}
}
题外话
java有指针吗?其实java到处都是指针,但跟C语言不同的是,C语言可以显式的声明并使用指针,但是java的没有也不允许声明和使用显式指针,但是java中对一个对象的访问却是通过指针来实现的,我们称之为隐式指针。但是上面说到的left
指针和right
指针并不能算是指针,只是作为遍历数组的两个标志位。
算法其实就是找出规律,用代码翻译这个规律,结合其他的数据结构,使得翻译出来的代码执行的效率更高。
搬砖不易,转载请注明出处。