面试题 08.06. 汉诺塔问题
Tower of Hanoi
相传在很久以前,有个寺庙里的几个和尚整天不停地移动着 64 个盘子,日复一日,年复一年。据说,当 64 个盘子全部移完的那一天就是世界末日...
问题描述
有 A,B,C 三根柱子,A 上面有 n 个盘子,我们想把 A 上面的盘子移动到 C 上,但是要满足以下三个条件:
-
每次只能移动一个盘子;
-
盘子只能从柱子顶端滑出移到下一根柱子;
-
盘子只能叠在比它大的盘子上。
解题思路:递归与分治
这是一道递归方法的经典题目,乍一想还挺难理清头绪的,我们不妨先从简单的入手。
假设 n = 1
只有一个盘子,很简单,直接把它从 A 中拿出来,移到 C 上;
如果 n = 2
呢?这时候我们就要借助 B 了,因为小盘子必须时刻都在大盘子上面,共需要 4 步。
如果 n > 2
呢?思路和上面是一样的,我们把 n 个盘子也看成两个部分,一部分有 1 个盘子,另一部分有 n - 1 个盘子。
观察上图,你可能会问:“那 n - 1 个盘子是怎么从 A 移到 C 的呢?”
注意,当你在思考这个问题的时候,就将最初的 n 个盘子从 A 移到 C 的问题,转化成了将 n - 1 个盘子从 A 移到 C 的问题, 依次类推,直至转化成 1 个盘子的问题时,问题也就解决了。这就是分治的思想。
而实现分治思想的常用方法就是递归。不难发现,如果原问题可以分解成若干个与原问题结构相同但规模较小的子问题时,往往可以用递归的方法解决。具体解决办法如下:
-
n = 1
时,直接把盘子从 A 移到 C; -
n > 1
时,- 先把上面 n - 1 个盘子从 A 移到 B(子问题,递归);
- 再将最大的盘子从 A 移到 C;
- 再将 B 上 n - 1 个盘子从 B 移到 C(子问题,递归)。
代码如下:
package main
func hanota(A []int, B []int, C []int) []int {
if A == nil{
return A
}
move(len(A),&A,&B,&C)
return C
}
// n:要移动的盘子数目a. A,B,C分别代表当前情况下的第1,2,3个柱子
func move(n int ,A,B,C *[]int){
if n==1{
*C=append(*C,(*A)[len(*A)-1])// 不能直接写(*A)[len(0)-1],因为最底下可能存在对移动不影响的大盘子
*A=(*A)[:len(*A)-1]
}else {
// 将除最底下的盘子的上面的移动到第二个
move(n-1,A,C,B)
//把最底下的由第一个移动到第三个
move(1,A,B,C)
//把第二个整体移动到第三个
move(n-1,B,A,C)
}
}
func main(){
}