在平时找工作的时候,或多或少会遇到一些算法问题,很多都是比较经典或者网上已经流传很久的。只是我们没有接触过,所以不知道怎么解决。

  在这儿,我自己总结一些我遇到的一些经典算法,给自己增加一点记忆,也给需要的朋友看到学习一下。

1. 倒水问题

如题:一个容量为5升的杯子和一个容量为3升的杯子,水不限使用,要求精确得到4升水。

  这类题一般会有两种出题方式:

  A.简答

    这儿先给出简答的答案:其实结果又很多种,这儿给出倒水次数最少的一种。

    

  B.编程实现

    解法也比较多,我首先想到的DFS(深度优先)搜索,每次我们有6种选择,只要不断的尝试下去,总可以搜索完所有的状态,找到一个解。也可以用宽度优先搜索(BFS)。

  程序代码后续补上。

  后续也有其他版本的倒水问题,如:有三个容器,分别为20升,13升,7升。20升的容器装满水,要使20升和13升的容器里分别装10水,如何做到?

    简答的步骤和前面类似:

    

2.猴子选大王问题

题目:

n只猴子围坐成一个圈,按顺时针方向从1到n编号。
然后从1号猴子开始沿顺时针方向从1开始报数,报到m的猴子出局,再从刚出局猴子的下一个位置重新开始报数,
如此重复,直至剩下一个猴子,它就是大王。

设计并编写程序,实现如下功能:
(1)   要求由用户输入开始时的猴子数$n、报数的最后一个数$m。
(2)   给出当选猴王的初始编号。

这个题直接想到的就是循环链表实现,或者数组实现。

下面直接贴出代码:

 解法1:

 <?php
$arr = array('1','2','3','4','5','6','7');//示例数组
echo '<pre>The King is :<br/>';
print_r(king($arr,11));
function king($arr,$count){
$i = 1;//从1开始
while(count($arr) > 1){
if($i%$count == 0){//用求余,计算数到的位,如果求余为0,所数到的位消除,压出数组中
unset($arr[$i-1]);
}else{//数到的位不是结束,把这一位放到数组末尾,并消掉这个位
array_push($arr,$arr[$i-1]);
unset($arr[$i-1]);
}
$i++;//转移到下一个数组元素
15   }
return $arr;
}

这种实现方式是使用数组,如果当前数不是选中的,则将其移到数组最后。算法复杂度较高,当数据量较大时,处理效率低。

解法2:

 /**
*
* @param int $n
* 开始时的猴子数量
* @param int $m
* 报道的最后一个数
* (报到这个数的猴子被淘汰,然后下一个猴子重新从①开始报数)
* @return int 猴子的初始编号
*/
function monkeySelectKing($n, $m)
{
// 猴子的初始数量不能小于2
if ($n < 2) {
return false;
} $arr = range(1, $n);
// 将猴子分到一个数组里, 数组的值对应猴子的初始编号
$unsetNum = 0;
// 定义一个变量,记录猴子的报数 for ($i = 2; $i <= $n * $m; $i ++)
// 总的循环次数不知道怎么计算,
{
// 不过因为循环中设置了return,所以$m*$len效率还可以
foreach ($arr as $k => $v) {
$unsetNum ++; // 每到一个猴子, 猴子报数+1 // 当猴子的报数等于淘汰的数字时:淘汰猴子(删除数组元素)
// 报数归0(下一个猴子从1开始数)
if ($unsetNum == $m) {
// echo "<pre>";//打开注释,可以看到具体的淘汰过程
// print_r($arr);
unset($arr[$k]);
// 淘汰猴子
$unsetNum = 0;
// 报数归零
if (count($arr) == 1)
// 判断数组的长度, 如果只剩一个猴子, 返回它的值
{
return reset($arr);
}
}
}
}
} var_dump(monkeySelectKing(100, 2));

这种算法实现的复杂度为O(nm);当nm比较大时,这个效率就比较低。

更简单的实现方式:也叫约瑟夫环的数学解法

原理

无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较烦,而且时间复杂度高达O(nm),当n,m非常大(例如上百万,上千万)的时候,几乎是没有办法在短时间内出结果的。我们注意到原问题仅仅是要求出最后的胜利者的序号,而不是要读者模拟整个过程。因此如果要追求效率,就要打破常规,实施一点数学策略。
为了讨论方便,先把问题稍微改变一下,并不影响原意:

  问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号。

  我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始):
      k  k+1  k+2  ... n-2, n-1, 0, 1, 2, ... k-2
  并且从k开始报0。

现在我们把他们的编号做一下转换:
k     --> 0
k+1   --> 1
k+2   --> 2
...
...
k-2   --> n-2
k-1   --> n-1

变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x'=(x+k)%n

如何知道(n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的情况 ---- 这显然就是一个倒推问题!好了,思路出来了,下面写递推公式:

令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]

递推公式
f[1]=0;
f[i]=(f[i-1]+m)%i;  (i>1)

有了这个公式,我们要做的就是从1-n顺序算出f[i]的数值,最后结果是f[n]。因为实际生活中编号总是从1开始,我们输出f[n]+1

最后得出的解法就是:

 /**
* 约瑟夫环的数学解法
*/
function yuesefu($n,$m) {
$r=0;
for($i=2; $i<=$n; $i++) { $r=($r+$m)%$i;
}
return $r+1;
}
print_r(yuesefu(100,2));

有关约瑟夫环的数学解法来自于:http://www.cppblog.com/Victordu/archive/2008/02/22/43082.html

最新文章

  1. JS中的get &amp; set
  2. 16.iOS APP图标和启动画面尺寸
  3. 转 C# 装箱和拆箱[整理]
  4. [cocos2d-x]OPENGL ES支持的像素格式
  5. SQL还原备份数据库读取失败 38错误解决办法
  6. Extjs Cmd 学习笔记
  7. Checkbox框全选操作,form表单提交与jquery ajax提交两种处理方式
  8. this 函数内部属性
  9. hdoj 1253 胜利大逃亡
  10. vscode 开发.net core 从安装到部署 教程详解
  11. Python使用Scrapy爬虫框架全站爬取图片并保存本地(妹子图)
  12. Spring MVC的handlermapping之RequestMappingHandlerMapping初始化
  13. Python后台开发Django(启动)
  14. FreeNas搭建踩坑指南(三)
  15. Web前端3.0时代,“程序猿”如何“渡劫升仙”
  16. Windows和MacOS的比较——不断完善和补充,欢迎吐槽
  17. BZOJ4553/洛谷P4093 [HEOI2016/TJOI2016]序列 动态规划 分治
  18. Java 8 中常用的函数式接口
  19. JavaScript大杂烩18 - Web开发的MVVM模式
  20. linux 播放加密DVDs

热门文章

  1. 设计模式-(11)组合模式 (swift版)
  2. Ural2089:Experienced coach(二分图匹配)
  3. maven中添加json-lib的jar包
  4. ExtJS4 带清除功能的文本框 triggerfield
  5. win8 使用notepad++写C代码
  6. 洛谷P4216 [SCOI2015]情报传递(树剖+主席树)
  7. [Swift]编码拾遗
  8. CAD中的文本编排操作
  9. SIFT特征点检测与匹配
  10. shell脚本从入门到精通