描述
题目描述
我们有由底至上为从大到小放置的 n 个圆盘,和三个柱子(分别为左/中/右即left/mid/right),开始时所有圆盘都放在左边的柱子上,按照汉诺塔游戏的要求我们要把所有的圆盘都移到右边的柱子上,要求一次只能移动一个圆盘,而且大的圆盘不可以放到小的上面。 请实现一个函数打印最优移动轨迹。
给定一个 int n
,表示有 n 个圆盘。请返回一个 string
数组,其中的元素依次为每次移动的描述。描述格式为: move from [left/mid/right] to [left/mid/right]
。
要求:时间复杂度 O(3^n), 空间复杂度 O(3^n)
示例
输入:2
返回值:
["move from left to mid","move from left to right","move from mid to right"]
知识点:递归,汉诺塔 难度:⭐⭐⭐
题解
方法一:递归
解题思路:
可以通过定义函数:Hanoi(int n, String left, String mid, String right, List<String> res)
,其递归功能为:圆盘从left到right,打印其move过程,即通过在递归过程中不断修改 left 和 right 参数,满足移动的规则
算法流程:
-
定义并明确递归函数功能:圆盘从left到right,打印其move过程
-
递归终止条件:递归到 left 最下面的圆盘,不进行处理,结束递归
-
1、对于第2个到第n个圆盘,先借助right,在满足规则的情况下,将圆盘移动到mid
-
2、对于left最下面圆盘,直接从left到right
-
3、对于第2个到第n个圆盘,借助mid,将圆盘根据规则移动到right
Java 版本代码如下:
import java.util.*;
public class Solution {
public ArrayList<String> getSolution(int n) {
// write code here
ArrayList<String> res = new ArrayList<>();
Hanoi(n, "left", "mid", "right", res);
return res;
}
// 递归函数功能:圆盘从left到right,打印其move过程
public void Hanoi(int n, String left, String mid, String right, List<String> res) {
// 递归终止条件: 递归到left最下面的圆盘,不进行处理,结束递归
if(n == 0) {
return;
}
// 1、对于第2个到第n个圆盘,先借助right,在满足规则的情况下,将圆盘移动到mid
Hanoi(n - 1, left, right, mid, res);
// 2、对于left最下面圆盘,直接从left到right
// 根据设定的参数进行打印
res.add("move from " + left + " to " + right);
// 3、对于第2个到第n个圆盘,借助mid,将圆盘根据规则移动到right
Hanoi(n - 1, mid, left, right, res);
}
}
复杂度分析:
时间复杂度 :递归次数为2^n, 递归计算时间为O(1),总的为O(2^n),n为圆盘个数
空间复杂度 :栈需要的递归深度
总结:
一定要明确递归函数功能并且不怀疑其功能
方法二:非递归
解题思路:
1、以 n = 3 为例,从当前柱子A开始,从 A 将顶部的最小盘子移动到顺时针下一个柱子C
2、将当前柱子A与另一根柱子B比较圆盘长度,如果柱子 B 没有圆盘或B的顶部圆盘大于A的顶部圆盘,则将A的顶部圆盘移动到B,随后顺时针地将当前指针指向下一个柱子,即柱子B
3、同样地,当前柱子C与顺时针下一个柱子进行圆盘比较,满足汉诺塔规则,将圆盘移动到柱子B,再与另一根柱子A比较,因为每次都要移动,而柱子C没有圆盘,则将柱子A的圆盘移动到柱子C,随后cur指针顺时针指向柱子B,如图:
4、同样地,柱子B与柱子A和C分别进行比较,移动后如图,随后指针顺时针旋转到柱子A
5、最后,柱子A顺时针比较柱子C,满足汉诺塔规则,移动到柱子C,此时发现有柱子C满了,则直接返回结果
C++ 版本代码如下:
class Solution {
public:
vector<string> solution;
typedef struct
{
string name;
vector<int> stack;
}stack;
void initStack(stack &a, stack &b, stack &c, int n)
{
for (int i = n; i >= 1; -- i){
a.stack.push_back(i);
}
a.name = "left";
// 当n为奇数时,顺时针的柱子顺序为left,mid,right
if (n % 2 == 1){
b.name = "right";
c.name = "mid";
}
// 当n为偶数时,顺时针的柱子顺序为left,right,mid
else{
b.name = "mid";
c.name = "right";
}
}
void move(stack &a, stack &b)
{
// 将a中的栈顶元素移入b中
solution.push_back("move from " + a.name + " to "+b.name);
b.stack.push_back(a.stack.back());
a.stack.pop_back();
}
// 算法的第二步就是对于输入的两个栈,将其中一个栈的栈顶元素移到另一个栈中
void moveOneToOne(stack &a, stack &b)
{
// 哪个柱子没有圆盘,则直接移动圆盘
if (a.stack.empty()){
move(b, a);
}else if (b.stack.empty()){
move(a, b);
}
// 实现汉诺塔规则
// a的栈顶元素如果大于b的,自然只能把b的元素移到a中,否则a的圆盘移动到B的圆盘
else if (a.stack.back() > b.stack.back()){
move(b, a);
}
else{
move(a, b);
}
}
// 若有一根柱子满了,则循环结束
bool isEnd(stack &b, stack &c, int n)
{
if (b.stack.size() == n || c.stack.size() == n){
return true;
}
else{
return false;
}
}
void hanoi(int n, stack &A, stack &B, stack &C)
{
// 实现指针顺时针旋转指向
for (int i = 0;!isEnd(B, C, n); ++ i){
if (i % 3 == 0){
move(A, B);
if (isEnd(B, C, n)){
break;
}
moveOneToOne(A, C);
}
else if (i % 3 == 1)
{
move(B, C);
if (isEnd(B, C, n)){
break;
}
moveOneToOne(B, A);
}
else{
move(C, A);
if (isEnd(B, C, n)){
break;
}
moveOneToOne(C, B);
}
}
}
vector<string> getSolution(int n) {
stack A,B,C;
// 初始化
initStack(A, B, C, n);
hanoi(n, A, B, C);
return solution;
}
};
复杂度分析:
时间复杂度 :总的圆盘移动次数的时间复杂度为 O(2^N)
空间复杂度 :需要借助长度为N的栈