(1)接雨水问题
说明:如下图,每个元素表示柱子的高度,黑色的为柱子,灰色的为接到的雨水。给定一个数组之后,求可接的雨水是多少。
代码:
1 <?php 2 $arr = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]; 3 echo rainVolume($arr); 4 5 function rainVolume($arr) { 6 static $res = 0; 7 if (count($arr) <= 2) { 8 return ; 9 } 10 $bak = $arr; 11 arsort($arr); 12 13 //取前两名 14 $two = array_slice(array_keys($arr), 0, 2); 15 sort($two); 16 $res += celVolume(array_slice($bak, $two[0], $two[1] - $two[0] + 1)); 17 18 //分为两部分 19 rainVolume(array_slice($bak, 0, $two[0] + 1)); 20 rainVolume(array_slice($bak, $two[1], count($bak) - $two[1])); 21 return $res; 22 } 23 24 function celVolume($arr) { 25 $lines = [array_shift($arr), array_pop($arr)]; 26 return count($arr) * min($lines) - array_sum($arr); 27 }
ps:半年前写的一版,这个乱。。。
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);
(2)1000个苹果
说明:一千个苹果放到10个框内,如何放置才能查出1~1000内任意数量的苹果。
解题:
现在第一个篮子里放1个苹果。
此时我们已经可以表达1了,但是表达不了2,所以在第二个篮子里放2个苹果
此时我们可以表达1,2,3,但是表达不了4,所以在第三个篮子里放4个苹果
此时我们可以表达1,2,3,4,5,6,7 但是表达不了8,所以在第四个篮子放8个苹果
。。。
可以看出规律,分别是1,2,4,8,16,32.......
细想一下原因,为什么会这样。其实和2进制转10进制原理相同。举个例子,假如按照上面的规则摆放完毕,怎么可以取出419个苹果?
首先将419转为2进制,结果为110100011,答案就是取出第1,2,6,8,9个篮子的苹果加到一起(值为1的位)。
但是如果想把10个篮子摆满要1023个,我们的苹果不够。如果只有9个篮子我们可以表示的范围是1~511。
1 <?php 2 $r = 0; 3 for ($i=0; $i<9; $i++) { 4 $r += pow(2, $i); 5 } 6 echo $r; //511 7 echo ($r + pow(2, $i)); //1023
所以第十个篮子要摆放1000 - 511 = 489个苹果。如果要表示1~511,直接从前9个篮子取就可以了;如果想要表示511~1000,需要前9个篮子取一部分再加上第10个篮子。大致区间划分:
前9个篮子:[1, 511]
全部篮子:[1, 511] ∪ [489+1, 489+511] = [1, 1000]