【LintCode·容易】用栈模拟汉诺塔问题
用栈模拟汉诺塔问题
描述
在经典的汉诺塔问题中,有 3 个塔和 N 个可用来堆砌成塔的不同大小的盘子。要求盘子必须按照从小到大的顺序从上往下堆 (如:任意一个盘子,其必须堆在比它大的盘子上面)。同时,你必须满足以下限制条件:
- 每次只能移动一个盘子。
- 每个盘子从堆的顶部被移动后,只能置放于下一个堆中。
- 每个盘子只能放在比它大的盘子上面。
请写一段程序,实现将第一个堆的盘子移动到最后一个堆中。
样例
输入
3
输出
towers[0]: []
towers[1]: []
towers[2]: [2,1,0]
思路
因为自己以前没玩过,也没有纸和笔,另外一直在想知道每个步骤到底应该怎么做,应该从哪个移到哪个,所以到最后也没有想出一个可行的办法,最后迫不得已去搜了一下,结果才发现自己一直在钻牛角尖:
当只有一个盘子的时候,只需要从将A塔上的一个盘子移到C塔上。
当A塔上有两个盘子是,先将A塔上的1号盘子(编号从上到下)移动到B塔上,再将A塔上的2号盘子移动的C塔上,最后将B塔上的小盘子移动到C塔上。
当A塔上有3个盘子时,先将A塔上编号1至2的盘子(共2个)移动到B塔上(需借助C塔),然后将A塔上的3号最大的盘子移动到C塔,最后将B塔上的两个盘子借助A塔移动到C塔上。
当A塔上有n个盘子是,先将A塔上编号1至n-1的盘子(共n-1个)移动到B塔上(借助C塔),然后将A塔上最大的n号盘子移动到C塔上,最后将B塔上的n-1个盘子借助A塔移动到C塔上。
综上所述,除了只有一个盘子时不需要借助其他塔外,其余情况均一样(只是事件的复杂程度不一样)。
看过上面这段话,应该都能写出来代码了,不过为了照顾以后的自己,我还是把我自己的思路写下来吧:
三个塔:A,B,C。需要把盘子从A中移动到C中。
三个塔在每次移动时每个都有一个身份,分别为“源塔”(最开始盘子最多的塔)、“目标塔”(最后要把所有的盘子堆起来的塔)、“辅助塔”(与前面两个相对而言),每个塔与身份并不固定。
当只有一个盘子的时候:直接把A(源塔)中的盘子放到C(目标塔)中。结束!
当有两个盘子的时候【目标:把A(源塔)的两个盘子放到C(目标塔)中】:只用三步:
- 把A(源塔)中最小的盘子放到B(辅助塔)中
- 再将A(源塔)中的第二个盘子放到C(目标塔)中
- 最后再把B(辅助塔)中的最小的盘子放到C(目标塔)中
结束!
当有三个盘子的时候:用两个盘子时的思维,只用三步:
- 我们只用将A(源塔)中的前两个盘子放到B(辅助塔)中
- 再将A(源塔)中第三个盘子放到C(目标塔)中
- 最后再把B(辅助塔)中的两个盘子放到C(目标塔)中
结束——就怪了。因为还没考虑怎么将A的两个盘子放到B中,所以这个时候的目标就变了,变成了把A中的前两个盘子放到B中,这时候就很明显了,只要把塔的身份变换一下,就和两个盘子时的操作一模一样了。之后第二步也很简单。到了第三步,这个时候,A没有盘子,B有两个盘子,C有一个最大的盘子,这个时候的目标是把B中的两个盘子放到C中。是的,和两个盘子时的目标又是类似,所以替换一下身份再来一次,问题就真的解决了!
所以,当有N个盘子的时候,我们也只要三步:
- 把A的N-1个盘子放到B中
- 把A所剩下唯一的也是最大的盘子放到C中
- 把B中所有的盘子都放到C中
对于第一步,我们又可以转化为这三步:
此时的目标是把盘子从A放到B中
- 把A的N-2个盘子放到C中
- 把A最大的盘子(对于那N-1个盘子来说)放到B中
- 把C中所有的盘子都放到B中
对于这个的第一步,我们又可以……
由此,我们就可以写出代码:
代码
其中的moveTopTo
和moveDisks
方法为需要填补的内容
public class Tower {
private Stack<Integer> disks;
// create three towers (i from 0 to 2)
public Tower(int i) {
disks = new Stack<Integer>();
}
// Add a disk into this tower
public void add(int d) {
if (!disks.isEmpty() && disks.peek() <= d) {
System.out.println("Error placing disk " + d);
} else {
disks.push(d);
}
}
// @param t a tower
// Move the top disk of this tower to the top of t.
public void moveTopTo(Tower t) {
// Write your code here
if (t.disks.isEmpty() || (!disks.isEmpty() && t.disks.peek() >= disks.peek())) {
t.disks.push(disks.pop());
}
}
// @param n an integer
// @param destination a tower
// @param buffer a tower
// Move n Disks from this tower to destination by buffer tower
public void moveDisks(int n, Tower destination, Tower buffer) {
// Write your code here
if (n <= 0) {
return;
} else if (n == 1) {
moveTopTo(destination);
} else {
moveDisks(n - 1, buffer, destination); //将源塔前几个盘子都放到辅助塔中
moveDisks(1, destination, buffer); //此时源塔只剩下一个最大的盘子,将它放到目标塔中
buffer.moveDisks(n - 1, destination, this); //最后再将辅助塔的全部盘子(因为此时目标塔上已经有了一个盘子,故辅助塔的盘子数为n-1)都放到目标塔上
}
}
public Stack<Integer> getDisks() {
return disks;
}
}
/**
* Your Tower object will be instantiated and called as such:
* Tower[] towers = new Tower[3];
* for (int i = 0; i < 3; i++) towers[i] = new Tower(i);
* for (int i = n - 1; i >= 0; i--) towers[0].add(i);
* towers[0].moveDisks(n, towers[2], towers[1]);
* print towers[0], towers[1], towers[2]
*/
写在最后
这次可能写的思路有点啰嗦,不过自己感觉理了一遍以后思路更加清晰了,这个方法是用的递归,我也看了非递归的方法,不过感觉非递归的思想感觉比这个更难理解,所以就放弃了,先把思路复制到这里,等以后有空的话再试着理一理吧。
其实算法非常简单,当盘子的个数为n时,移动的次数应等于2^n – 1(有兴趣的可以自己证明试试看)。后来一位美国学者发现一种出人意料的简单方法,只要轮流进行两步操作就可以了。首先把三根柱子按顺序排成品字型,把所有的圆盘按从大到小的顺序放在柱子A上,根据圆盘的数量确定柱子的排放顺序:若n为偶数,按顺时针方向依次摆放 A B C;若n为奇数,按顺时针方向依次摆放 A C B。
⑴按顺时针方向把圆盘1从现在的柱子移动到下一根柱子,即当n为偶数时,若圆盘1在柱子A,则把它移动到B;若圆盘1在柱子B,则把它移动到C;若圆盘1在柱子C,则把它移动到A。
⑵接着,把另外两根柱子上可以移动的圆盘移动到新的柱子上。即把非空柱子上的圆盘移动到空柱子上,当两根柱子都非空时,移动较小的圆盘。这一步没有明确规定移动哪个圆盘,你可能以为会有多种可能性,其实不然,可实施的行动是唯一的。
⑶反复进行⑴⑵操作,最后就能按规定完成汉诺塔的移动。
所以结果非常简单,就是按照移动规则向一个方向移动金片:
如3阶汉诺塔的移动:A→C,A→B,C→B,A→C,B→A,B→C,A→C
来自汉诺塔_百度百科
【LintCode·容易】用栈模拟汉诺塔问题的更多相关文章
- Python使用函数模拟“汉诺塔”过程
运行效果: 源代码: 1 # -*- coding:utf-8 -*- 2 ##汉诺塔游戏开始 3 _times=0 #用于统计移动次数 4 def hannuota(nlist,mfrom,mpas ...
- [javascript]模拟汉诺塔
看了博文自己动手写了代码. 这能值几个钱? 请写代码完成汉诺塔的算法:void Hanoi(int maxLevel); 比如2层汉诺塔,需要打印(Console.WriteLine)出如下文本: A ...
- 算法笔记_013:汉诺塔问题(Java递归法和非递归法)
目录 1 问题描述 2 解决方案 2.1 递归法 2.2 非递归法 1 问题描述 Simulate the movement of the Towers of Hanoi Puzzle; Bonus ...
- Java实现汉诺塔问题
1 问题描述 Simulate the movement of the Towers of Hanoi Puzzle; Bonus is possible for using animation. e ...
- js模拟栈---汉诺塔
var Stack = (function(){ var items = new WeakMap(); //先入后出,后入先出 class Stack{ constructor(){ items.se ...
- LintCode 汉诺塔
题目链接:https://www.lintcode.com/problem/tower-of-hanoi/description 题目大意 经典递归问题. 分析 由于是经典问题了,这里不讨论用递归实现 ...
- 数据结构--汉诺塔--借助栈实现非递归---Java
/*汉诺塔非递归实现--利用栈 * 1.创建一个栈,栈中每个元素包含的信息:盘子编号,3个塔座的变量 * 2.先进栈,在利用循环判断是否栈空, * 3.非空情况下,出栈,检查是否只有一个盘子--直接移 ...
- 汉诺塔问题II(模拟)
汉诺塔问题II Time Limit: 1 Sec Memory Limit: 64 MB Submit: 1556 Solved: 720 Description 汉诺塔(又称河内塔)问题是源于 ...
- 汉诺塔VII(递推,模拟)
汉诺塔VII Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submis ...
随机推荐
- oracle建表权限问题和JSP连接oracle数据库基本操作
JSP连接oracle数据库相关操作 1.创建表 打开Enterprise Manager Console,为用户添加权限CREATE ANY TABLE和分配一定的表空间USERS限额1024k. ...
- react-native绑定优酷SDK播放视频-附效果和git源码
ReactNative绑定优酷SDK需要用到两部分知识: 优酷本身的sdk绑定: RN与原生界面的交互: 效果: RN版本:0.49.3 代码更新日期:2017.10.26 下文也根据绑定优酷需要的两 ...
- 在Owin Self-Hosing下实现每个请求中共享上下文(数据)
问题 这几天在做公司的外部WebApi网关,由于使用了OAuth2.0,所以不得不使用Owin来部署网关. 而涉及到请求上下文的问题,为了使业务层能获取到请求头的信息,又不与网关耦合,决定把请求信息写 ...
- linux安装redis-3.0.7
一.Redis介绍 1.简介 Redis是当前比较热门的NOSQL系统之一,它是一个key-value存储系统.和Memcache类似,但很大程度补偿了Memcache的不足,它支持存储的value类 ...
- js判断元素滑动方向(上下左右)移动端
每天学习一点点. 1 var startx, starty; //获得角度 function getAngle(angx, angy) { return Math.atan2(angy, angx) ...
- poj 3484 Showstopper
Showstopper Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 2236 Accepted: 662 Descri ...
- 2016 ACM/ICPC Asia Regional Dalian Online Football Games
Football Games Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)To ...
- Luogu 2296 寻找道路
https://www.luogu.org/problemnew/show/2296 题目描述 在有向图G 中,每条边的长度均为1 ,现给定起点和终点,请你在图中找一条从起点到终点的路径,该路径满足以 ...
- js 作用域,作用域链,闭包
什么是作用域? 作用域是一种规则,在代码编译阶段就确定了,规定了变量与函数的可被访问的范围.全局变量拥有全局作用域,局部变量则拥有局部作用域. js是一种没有块级作用域的语言(包括if.for等语句的 ...
- mouseover事件mouseenter事件
1. mouseover:会在鼠标进入该元素或者该元素的子元素时触发 mouseenter:只在鼠标进入该元素时触发 mouseout:在鼠标移出该元素或则该元素的子元素时触发 mouseleave: ...