接水滴算法
1.题目大意
给定 n 个非负整数表示每个宽度为1的柱子的高度,计算按此排列的柱子,下雨之后能接多少雨水。
2.辅助理解
例如有以下数组: [0,1,0,2,1,0,1,3,2,1,2,1]
该数组组成的对应图形如下图所示:
当雨水从天上落下后,上图中的凹槽将会被雨水注满,下图中蓝色部分表示最终积累的雨水:
从图中可以得出积累的雨水数量一共为6个正方形,也就是6个单位的雨滴。
大概的要求已经知道了,那么这个算法应该如何实现呢?
3.求解方案
根据水滴图形最终的组成规律,最终组成的图形一定不会出现凹状,那么积累的水滴数量我们可以用最终形成的图形的单位数量减去初始形状的单位数量
,最终得到的差值就是积累的水滴数量。
最终图形的单位数量一共包括以下三部分,分别是:
-
- 填补后最高点组成的形状
- 填补后最高点左侧的形状
- 填补后最高点右侧的形状
3.1 填补最高点组成的形状
填补前最高点组成的形状有两种情况,分别是最高点下标只有一个和最高点下标不止一个,
如数组[1,2,3,2,3,1]的最高点下标不止一个,它的最高点最终会变成[1,2,3,3,3,1],图形最高点围成的总数量为3+3+3=9;
而数组[1,2,3,4,3,1]的最高点只有一个,图形最高点围成的总数量为4;
以下是获得数组中的最大值和最大值对应的下标的list
/** * 获取数组中的最大值和对应的下标 * * @param arr * @return */ private List returnMaxIndex(int[] arr) { int max = arr[0]; for (int i = 1; i <= arr.length; i++) { if (max < arr[i - 1]) { max = arr[i - 1]; } } for (int i = 0; i < arr.length; i++) { if (arr[i] == max) maxIndex.add(i); } maxValue = max; return maxIndex; }
以下代码为设置最大值对应下标之间的元素为最大值
maxIndexArr = returnMaxIndex(arr); if (maxIndexArr.size() > 1) { //设置最大值对应下标之间的元素为最大值 for (int j = (int) maxIndexArr.get(0); j <= (int) maxIndexArr.get(maxIndexArr.size() - 1); j++) { arr[j] = maxValue; }}
3.2 填补最高点左侧的形状
填补的方法是使用递归,从最高点对应的最小
下标开始向左
移动,如果前一个数小于当前下标对应的数,那么跳过,如果前一个数大于当前下标对应的数,那么当前下标对应值+1,最终数组左侧被填满
,代码如下:
private void fixLeftValue(int[] arr, int minIndex) { for (int m = minIndex; m > 0; m--) { if (arr[m] < arr[m - 1]) { arr[m] += 1; fixLeftValue(arr, minIndex); } } }
3.3 填补最高点右侧的形状
填补最高点右侧的方法和左侧的类似,从最高点对应的最大
下标开始向右
移动,如果后一个数小于当前下标对应的数,那么跳过,如果后一个数大于当前下标对应的数,那么当前下标对应值+1,最终数组右侧被填满
,代码如下:
private void fixRightValue(int[] arr, int maxIndex) { for (int m = maxIndex; m < arr.length - 1; m++) { if (arr[m] < arr[m + 1]) { arr[m] += 1; fixRightValue(arr, maxIndex); } } }
最后只需要用 填补后的数组的总单位数量 - 初始状态数组总单位数量,即可得到积累的水滴数。
完整代码如下:
import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import java.util.ArrayList; import java.util.List; @SpringBootTest class raindropsTest{ List maxIndex = new ArrayList(); private static int maxValue; @Test void rainDropsTestShouldSuccess() { List maxIndexArr; //水滴算法 int[] arr = {0,1,0,2,1,0,1,3,2,1,2,1}; int result1 = 0; int result2 = 0; for (int x : arr) { result1 += x; } maxIndexArr = returnMaxIndex(arr); if (maxIndexArr.size() > 1) { //设置最大值对应下标之间的元素为最大值 for (int j = (int) maxIndexArr.get(0); j <= (int) maxIndexArr.get(maxIndexArr.size() - 1); j++) { arr[j] = maxValue; } } //left fixLeftValue(arr, (int) maxIndexArr.get(0)); //right fixRightValue(arr, (int) maxIndexArr.get(maxIndexArr.size() - 1)); for (int k : arr) { result2 += k; } System.out.println("雨滴一共积累:" + (result2 - result1) + "滴"); } /** * 获取数组中的最大值对应的下标 * * @param arr * @return */ private List returnMaxIndex(int[] arr) { int max = arr[0]; for (int i = 1; i <= arr.length; i++) { if (max < arr[i - 1]) { max = arr[i - 1]; } } for (int i = 0; i < arr.length; i++) { if (arr[i] == max) maxIndex.add(i); } maxValue = max; return maxIndex; } private void fixLeftValue(int[] arr, int minIndex) { for (int m = minIndex; m > 0; m--) { if (arr[m] < arr[m - 1]) { arr[m] += 1; fixLeftValue(arr, minIndex); } } } private void fixRightValue(int[] arr, int maxIndex) { for (int m = maxIndex; m < arr.length - 1; m++) { if (arr[m] < arr[m + 1]) { arr[m] += 1; fixRightValue(arr, maxIndex); } } } }