算法笔记-乱七八糟问题

(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);
View Code

 

 

(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]

posted @ 2019-10-15 15:49  Dahouzi  阅读(358)  评论(0编辑  收藏  举报