今天来解释一个用数组来代表桶容量,然后计算桶里能装水的多少,就如图
其实这个问题只要知道了解题点,就真的是一个很简单的问题,但是很多人就会走歪,比如我开始看到题之后下意识的就会想如何来求这个这个相邻的两个极值点,即是波峰波谷的问题,但这样就会变得非常麻烦的一个局部问题,而且最后的结束的条件也不好求
所以请记住,数组的问题有一个通解,前面也讲过,可以考虑第i位置下的上方有多少水,这个水量靠什么来决定,然后就会变成,求i位置下的左右两边的最大值中的那个较小值
我会显示四种解法,代码的展示中,注释解释的很详细,我就不多说了
package arraySubOrderQuestionCollection; /* * question : 给定一个数组,这个数组表示一个桶,把这个数组用柱状图表示,陷进去的部分是可以装水的,,现在问题是这个桶总共可以装多少水? * * thinking : 小技巧 : 拿到一个题,先不要按照题中的思维走,很容易陷阱去,先去简化这个题的要求,比如在数组的问题上,我们有一个通解,就是求在i位置上什么什么,套用过来,就是在i位置上面的可以有多少水?怎么来求这个水的量,现在问题就变得 * 简单了,就是求在i位置上的水量是由谁来决定的. * * solution : i位置上的水是由左右两边的最大值中的较小的那个值来决定的,所以我们现在要做的就是,当遍历数组的每一个位置上的时候,就得需要知道这个位置的左右两边的最大值的那个较小值,如果,这个较小值大于当前遍历的这个位置时,那就可以算出 * 当前位置上的水量是多少,最后加在一起就可以了。然后现在要解决的问题就变成了如何在遍历每一个位置上的时候,得到当前遍历位置上的两边的最大值中的较小值.下面会提供4种方法,分别从时间复杂度和空间复杂度上面来区分 * */ public class HowMuchWater { // 第一种方法 : 在遍历到每一个位置的时候就去找当前位置前面后后面的最大值 // 很明显 这个时间复杂度是 o(n^2),然后空间复杂度1 public static int waterYield_M1(int[] bucket){ if(bucket == null || bucket.length == 0){ return 0; } int waterSum = 0; // 总水量 int minSize = 0; // 在得出两边最大值后的比较下来的最小值 for(int i=1; i<bucket.length-1; ++i){ // 这里不考虑第一个位置和最后一个位置,因为这两个位置上是不可能可以盛有水的 int maxLeft = 0; // 前面的最大值 int maxright = 0; // 后面的最大值 minSize = 0; for(int j = 0; j<i; ++j){ maxLeft = Math.max(bucket[j], maxLeft); } for(int z = i+1; z<bucket.length; ++z){ maxright = Math.max(bucket[z], maxright); } minSize = Math.min(maxLeft, maxright); if(minSize > bucket[i]){ waterSum += (minSize - bucket[i]); }else{ waterSum += 0; } } return waterSum; } // 第二种方法 : 不得不吐槽一下第一种方法,在写的时候就很嫌弃这种每次都要重复来算前面和后面的最大值中的最小值的步骤,计算机肯定也很嫌弃,所以为什么不直接用两个数组来储存这个数组中i位置下的前面的最大值和后面的最大值,每次要用的时候就直接 // 拿出来用就可以了 // 可以得到这个时间复杂度变成了o(n),但是空间复杂度是2n,因为有两个空间辅助数组 public static int waterYield_M2(int[] bucket){ int waterSum = 0; // 两个辅助的数组来储存i位置上的两边的最大值 int[] maxLeft = new int[bucket.length]; int[] maxRight = new int[bucket.length]; maxLeft[0] = bucket[0]; maxRight[bucket.length-1] = bucket[bucket.length-1]; for(int i=1; i<bucket.length; ++i){ maxLeft[i] = Math.max(bucket[i], maxLeft[i-1]); } for(int i = bucket.length-2; i>=0; --i){ maxRight[i] = Math.max(bucket[i], maxRight[i+1]); } for(int i=1; i<bucket.length-1; ++i){ int minSize = Math.min(maxLeft[i], maxRight[i]); if(minSize - bucket[i] > 0){ waterSum += (minSize-bucket[i]); }else{ waterSum += 0; } } return waterSum; } // 第三种方法 : 其实也就是从第二种方法中做出的一小步,可以清楚的看到第二种方法中的第一个和第三个for循环明明是可以放在一起的,也就是说求左边的最大值可以在从头遍历求waterSum的时候同时得到,就可以少一个辅助的数组 // 时间复杂度 : o(n) 空间复杂度为 : n public static int waterYield_M3(int[] bucket){ int waterSum = 0; int maxLeft = bucket[0]; // 左边的最大值由一个变量替换 int[] maxRight = new int[bucket.length]; maxRight[bucket.length-1] = bucket[bucket.length-1]; for(int i=bucket.length-2; i>=0; --i){ maxRight[i] = Math.max(bucket[i], maxRight[i+1]); } for(int i = 1; i<bucket.length-1; ++i){ maxLeft = Math.max(maxLeft, bucket[i]); int minSize = Math.min(maxLeft, maxRight[i]); if(minSize > bucket[i]){ waterSum += (minSize-bucket[i]); }else { waterSum += 0; } } return waterSum; } // 第四种方法 : 这种方法就比较牛逼轰轰了,只需要几个变量就行了,就是需要两个指针,左右开工,往中间走,将两个最大值的辅助数组变成两个变量,每次开始的时候,其实都能确定遍历到当前值的左右的最大值的较小的那个值 // 时间复杂度 o(n) ,空间复杂度 1 public static int waterYield_M4(int[] bucket){ int maxLeft = 0; int maxRight = 0; int waterSum = 0; int l = 0; // 左右两个指针变量 int r = bucket.length-1; while (l < r){ maxLeft = Math.max(maxLeft, bucket[l]); maxRight = Math.max(maxRight, bucket[r]); int minSize = 0; if(maxLeft < maxRight){ minSize = maxLeft; l ++; if(minSize > bucket[l]){ waterSum += (minSize-bucket[l]); }else{ waterSum += 0; } }else{ minSize = maxRight; r --; if(minSize > bucket[r]){ waterSum += (minSize-bucket[r]); }else{ waterSum += 0; } } } return waterSum; } public static void main(String[] args) { int[] bucket = {0,1,0,2,1,0,1,3,2,0,2,1}; System.out.println("the first method to calculate the water : "+waterYield_M1(bucket)); System.out.println("the second method to calculate the water : "+waterYield_M2(bucket)); System.out.println("the third method to calculate the water : "+waterYield_M3(bucket)); System.out.println("the fourth method to calculate the water : "+waterYield_M4(bucket)); } }