算法 蓄水问题
问题:
给定一个数组,想象成一个桶。问最多能装多少水?
例【1,5,3,6】 最多装2格水 O O ~ O O ~ O O O O O O O O O O O
解题思路:
我们把每一列当成一块板,根据分析,第一块板和最后一块板一定不能蓄水,所以问题变成了所有板所能蓄水最大值的总和。先明确这个思路,之后再想办法。
那么如何确定当前板可以蓄多少水呢?和它左右两边最长的板有关系。等于左右两边最长板的较小者减去当前板的长度和0取最大值。
表示为:
当前板蓄水量 = math.max(0, math.min(左最长板,右最长板) - 当前)
第一、最后一块板蓄水量 = 0
方法一:O(n^2)
简单暴力,挨个遍历每一块板,求出左右最大值,根据上面公式进行计算。不做演示
方法二:O(n)
根据方法一可知,时间复杂度主要在寻找每一块板的左右最大值。那么优化的方向就是使用O(n)复杂度获得每一块板的左右最大值。
怎么做呢?借助辅助数组。
例如【1,5,3,6】,则左边最大值数组为【1,5,5,6】,右边最大值数组为【6,6,6,6】
三个数组分别表示当前板长度,当前板的左边板最大长度、当前板的右边板最大长度。
public static int shui(int[] param) { if(param == null || param.length < 2) return 0; int[] L = new int[param.length]; int[] R = new int[param.length]; for(int i=1;i<param.length;i++) { L[i] = Math.max(param[i], param[i - 1]); } for(int i=param.length - 2;i>0;i--) { R[i] = Math.max(param[i], param[i + 1]); } int shui = 0; for(int i = 1; i<param.length-1;i++) { shui += Math.max(0, Math.min(L[i], R[i]) - param[i]); } return shui; }
方法三:O(n)
方法二空间复杂度太大,所以还有优化空间。
可以从两边同时进行判断,并且保存当前左右最大值。
如果左边最大值小于右边最大值,那么左边的当前板所能蓄水量就可确定 = math.max(0, 左最大值 - 当前)。并且当前索引++,标记左边最大值 = math.max(左最大值,当前板)。
如果右边最大值小于左边最大值,那么右边的当前板所能蓄水量就可确定 = math.max(0, 右最大值 - 当前)。并且当前索引--,标记右边最大值 = math.max(右最大值,当前板)。
如果相等,随便归属一边,或两边一起计算。
/* * 给定一个数组,想象成一个桶。问最多能装多少水 * 例【1,5,3,6】 最多装2格水 * O * O ~ O * O ~ O * O O O * O O O * O O O O * */ public static int shui(int[] param) { if(param == null || param.length < 2) return 0; int L = 1, R = param.length-2, shui = 0; int lmax = param[0], rmax = param[R + 1]; while(L<=R) { if(lmax <= rmax) { shui += Math.max(0, lmax - param[L]); lmax = Math.max(lmax, param[L++]); }else { shui += Math.max(0, rmax - param[R]); rmax = Math.max(rmax, param[R--]); } } return shui; } public static void main(String[] args) { int[] a = {2,1,3,5,2,6}; System.out.println(shui(a)); }