说明:如下图,每个元素表示柱子的高度,黑色的为柱子,灰色的为接到的雨水。给定一个数组之后,求可接的雨水是多少。

注意:(1)没有经过大量测试不敢保证没有漏洞   (2)只是个人想法,不敢保证最优

 1 <?php
 2 $arr = array(0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1);
 3        //key:0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
 4
 5
 6 //(1)首先要分块(装水的块),比如图中就分了三块。
 7 //至于怎么分呢,先找出最高的,例子中键值为7的元素最高;
 8 function findMax($arr)
 9 {
10     $val = current($arr);
11     $key = key($arr);
12     $i = -1; $pos = 0;         //记录位置(最大值是第几个元素,原数组的key值不可用),用于array_slice
13
14     foreach ($arr as $k => $v) {
15         $i++;
16         if ($v > $val) {
17             $val = $v;
18             $key = $k;
19             $pos = $i;
20         }
21     }
22     return compact('key', 'val', 'pos');
23 }
24
25 //这个方法用于不停的寻找代表围墙的元素,比如例子中代表墙的元素分别为下标是1,3,7,8,10的元素。
26 function findTags($arr, $dir = null){
27     static $resArr = array();   //用于存放标志元素
28     if (! $arr)
29         return array();
30
31     //查找最大值
32     $max = findMax($arr);
33     if ($max['val'] == 0)                                                       //全是0的话肯定是装不下水了
34         return;
35
36     if ($dir === null) {
37         $resArr[] = $max['key'];                                                //这个是最高的
38
39         //(2)然后以这个坐标为分割线,左边分为一个数组。然后找出这个数组的最大值,例子中的键值为3的元素最高(这两个元素可以围出来一块,可以存水)
40         //同样,不仅要往左找,还要往右找。找出所有这样的元素(相当于一面墙),放到一个数组中 $resArr。
41         findTags(array_slice($arr, 0, $max['key'], true), 1);                   //向左找最高的
42         findTags(array_slice($arr, $max['pos'] + 1, null, true), 2);             //向右找最高的
43         return $resArr;
44
45     }
46
47     //(3)不停的进行第(2)步,一直找到左右两边的尽头(最大值为0)为止。
48     elseif ($dir == 1) {
49         array_unshift($resArr, $max['key']);
50         findTags(array_slice($arr, 0, $max['key'], true), 1);                   //继续向左找
51     } elseif ($dir == 2) {
52         $resArr[] = $max['key'];
53         findTags(array_slice($arr, $max['pos'] + 1, null, true), 2);              //继续向右找
54     }
55 }
56
57 //(4)当统计出这些‘墙’之后,就要计算这些墙可以围多少水。大致思路:定义两个指针,分别指向两堵距离最近的墙,计算这两个墙可以围起的水,然后全部相加起来就行了
58 function calWalter($arr, $arrAll)
59 {
60     $count = 0;
61     $p1 = $arr;     //头指针
62     next($arr);
63     $p2 = $arr;     //尾指针
64
65     do {
66         $pp1 = current($p1); $pp2 = current($p2);
67         if ((current($p2) - current($p1)) != 1) {
68             //①计算可装下的水,底(两堵墙的距离) * 高(取较低的墙的高度) - 中间障碍(两堵墙中间的墙) 例如(2,1,3)可装下的水就是2*1-1=1
69             $high = $arrAll[$pp1] > $arrAll[$pp2] ? $arrAll[$pp2] : $arrAll[$pp1];
70             $count += ($pp2 - $pp1 - 1) * $high - array_sum(array_slice($arrAll, $pp1 + 1, $pp2 - $pp1 - 1));
71         }
72
73         //②如果两堵墙挨着是装不下水的。比如(1,2,2,1),所以如果有挨着的元素,首尾指针皆向下一位。
74         //或者没有挨着,我们计算完水容量之后也要向后移动
75         next($p1);
76         next($p2);
77     } while ($pp1 && $pp2);     //两堵墙要都存在是大前提
78
79     return $count;
80 }
81
82 echo calWalter(findTags($arr), $arr);
01-22 08:14