方法一:递归
此题为典型的递归问题。有3根柱子A、B、C我们想将A上的所有盘子挪到C上面,那么就必须先将上面的n-1个盘子先挪到B上面,再将A上最底下那个盘子挪到C上,最后再将B上的n-1个盘子挪到C上。
class Solution {
public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
this.doHanota(A, B, C, A.size());
}
/**
*count为需要挪动的盘子个数
*函数作用是将A上面的count个盘子挪到C上面
*
*/
private void doHanota(List<Integer> A, List<Integer> B, List<Integer> C, int count){
//A为空或者count为0,直接返回
if(A == null || A.size() == 0 || count == 0){
return;
}
//如果只需要移动一个盘子,直接操作
if(count == 1){
Integer x = A.remove(A.size() - 1);
C.add(x);
return;
}
//将A上面的count-1个盘子移动到B上面
this.doHanota(A, C, B, count - 1);
//移动A上面的第count个盘子到C上
this.doHanota(A, B, C, 1);
//移动B上面的count-1个盘子移动到C上面
this.doHanota(B, A, C, count - 1);
}
}
方法二:用栈模拟递归
class Solution {
static class Frame {
List<Integer> A; String a;
List<Integer> B; String b;
List<Integer> C; String c;
int n;
int stage; // 0: 准备做第1步, 1: 准备做第2步(移动), 2: 准备做第3步, 3: 结束
Frame(List<Integer> A, String a, List<Integer> B, String b, List<Integer> C, String c, int n) {
this.A = A; this.a = a;
this.B = B; this.b = b;
this.C = C; this.c = c;
this.n = n;
this.stage = 0;
}
}
public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
Deque<Frame> stack = new ArrayDeque<>();
stack.push(new Frame(A, "A", B, "B", C, "C", A.size()));
while (!stack.isEmpty()) {
Frame f = stack.peek();
if (f.n == 0) { // 等价于递归里的 count==0
stack.pop();
continue;
}
if (f.n == 1) { // 等价于递归里的 count==1
moveOne(f.A, f.a, f.C, f.c);
stack.pop();
continue;
}
if (f.stage == 0) {
// 第1步:H(A, C, B, n-1)
f.stage = 1;
stack.push(new Frame(f.A, f.a, f.C, f.c, f.B, f.b, f.n - 1));
} else if (f.stage == 1) {
// 第2步:move A -> C
f.stage = 2;
moveOne(f.A, f.a, f.C, f.c);
} else if (f.stage == 2) {
// 第3步:H(B, A, C, n-1)
f.stage = 3;
stack.push(new Frame(f.B, f.b, f.A, f.a, f.C, f.c, f.n - 1));
} else {
// stage == 3:这一帧三步都做完了
stack.pop();
}
}
}
private void moveOne(List<Integer> from, String fromName, List<Integer> to, String toName) {
// 题目保证合法移动;这里直接弹出栈顶即可
int x = from.remove(from.size() - 1);
to.add(x);
System.out.println(fromName + "->" + toName);
}
}
方法三:直接循环迭代
如果盘子数
n是 奇数:按对(A,C) -> (A,B) -> (B,C)循环如果
n是 偶数:按对(A,B) -> (A,C) -> (B,C)循环
class Solution {
public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
int n = A.size();
if (n == 0) return;
// n为奇数:AC, AB, BC
// n为偶数:AB, AC, BC
while (C.size() != n) {
if ((n & 1) == 1) {
moveBetween(A, "A", C, "C");
if (C.size() == n) break;
moveBetween(A, "A", B, "B");
if (C.size() == n) break;
moveBetween(B, "B", C, "C");
} else {
moveBetween(A, "A", B, "B");
if (C.size() == n) break;
moveBetween(A, "A", C, "C");
if (C.size() == n) break;
moveBetween(B, "B", C, "C");
}
}
}
// 在两根柱子之间做一次“唯一合法移动”
private void moveBetween(List<Integer> X, String xName, List<Integer> Y, String yName) {
if (X.isEmpty() && Y.isEmpty()) return;
if (X.isEmpty()) {
moveOne(Y, yName, X, xName);
return;
}
if (Y.isEmpty()) {
moveOne(X, xName, Y, yName);
return;
}
int topX = X.get(X.size() - 1);
int topY = Y.get(Y.size() - 1);
if (topX < topY) {
moveOne(X, xName, Y, yName);
} else {
moveOne(Y, yName, X, xName);
}
}
private void moveOne(List<Integer> from, String fromName, List<Integer> to, String toName) {
int disk = from.remove(from.size() - 1);
to.add(disk);
// 如果你不需要打印,删掉这一行即可
// System.out.println(fromName + "->" + toName);
}
}
移动次数解释说明
为什么移动次数是2^n - 1
从递归代码可以看出,移动次数为T(n)
T(n)=T(n−1)+1+T(n−1)=2T(n−1)+1
展开递推:
T(n)=2T(n−1)+1=2(2T(n−2)+1)+1=4T(n−2)+2+1=8T(n−3)+4+2+1…=2n−1T(1)+(2^(n−2)+2^(n−3)+⋯+2+1)
代入 T(1)=1T(1)=1:
T(n)=2^(n−1)+(2^(n−2)+⋯+2+1)
括号里是等比数列的前2n-2项,公比是2,代入等比数列求和公式 Sn = a1(1-q^n)/(1-q)得
2^(n−2)+⋯+2+1=2^(n−1)−1
T(n)=2^(n−1)+(2^(n−1)−1)=2^n −1

1512

被折叠的 条评论
为什么被折叠?



