这个问题是在《程序员代码面试指南》【书籍网址:https://www.nowcoder.com/tutorial/10016/index】上看到的,书上的解法思路有点繁琐了,从经典汉诺塔问题出发,我找到了一种更清晰的思路、更简洁的代码实现。
经典 汉诺塔 问题中,有 3 根柱子及 N 个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。初始状态下,所有盘子自上而下按升序依次套在第一根柱子上,要把所有盘子移动到最右边的柱子上,每次只能移动一个盘子且盘子只能叠在比它大的盘子上。
受限汉诺塔问题中,限制不能从最左侧的柱子直接移动到最右侧,也不能从最右侧直接移动到最左侧,而是必须经过中间的柱子。求当塔有N层的时候,打印出最优移动过程和最优移动总步数。
例如,当塔数为两层时,最上层的塔记为1,最下层的塔记为2,则打印:
Move 1 from left to mid
Move 1 from mid to right
Move 2 from left to mid
Move 1 from right to mid
Move 1 from mid to left
Move 2 from mid to right
Move 1 from left to mid
Move 1 from mid to right
It will move 8 steps.
经典汉诺塔问题
先从经典的汉诺塔问题看起
一阶汉诺塔过程
二阶汉诺塔过程
三阶汉诺塔过程
看左边的初始状态,因为 2 必须在 C 柱的最底端,所以首先的任务要把 0,1
移动到 B 柱上**【在ACB的顺序下执行二阶诺塔的移法 】**
再把 2 移动到 C 柱上【即把最大的圆盘从A移动到C 】
再把 0,1
移动到 C 柱上【即在BAC的顺序下执行二阶汉诺塔的移法 】
推广到任意N阶汉诺塔问题,都可以拆解为三个步骤:
- 在ACB的顺序下执行 N-1 阶汉诺塔的移法
- 把最大的圆盘从 A 移动到 C
- 在BAC的顺序下执行了 N-1 阶汉诺塔的移法
题目链接:https://leetcode-cn.com/problems/hanota-lcci/
class Solution {
public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
process(A, B, C, A.size());
}
public void process(List<Integer> A, List<Integer> B, List<Integer> C, int N) {
if (N == 0) return;
process(A, C, B, N - 1);
C.add(A.remove(A.size()-1));
process(B, A, C, N - 1);
}
}
用栈实现,打印移动过程
import java.util.Stack;
public class Main {
public static Stack<Integer> A = new Stack<>() {{
push(2); push(1); push(0);
}};
public static Stack<Integer> B = new Stack<>();
public static Stack<Integer> C = new Stack<>();
public static void main(String[] args) {
process(A, B, C, A.size());
}
public static void process(Stack<Integer> A, Stack<Integer> B, Stack<Integer> C, int N) {
if (N == 0) return;
process(A, C, B, N - 1);
System.out.printf("Move %d from %c to %c\n", A.peek(), getName(A), getName(C));
C.push(A.pop());
process(B, A, C, N - 1);
}
public static Character getName(Stack<Integer> S) {
//需要判断的是引用地址是否相等,这里要用==号来判断,不能用equals
if (S == A) return 'A';
else if (S == B) return 'B';
return 'C';
}
}
受限汉诺塔问题
三个步骤变为两个大步骤(五个小步骤):
用编号 1 , 2 , 3 , ⋯ , N 1,2,3,\cdots ,\rm N 1,2,3,⋯,N 分别代指 N \rm N N 个盘子
- 把
N
\rm N
N 号盘子(最底下的)从 A 移动到 C
- 把 A 柱上面 N − 1 \rm N-1 N−1 个盘子移动到 C【即以 ABC 顺序执行 N − 1 \rm N-1 N−1 阶汉诺塔】
- 把 N \rm N N 号盘子从A移动到B
- 把 C 柱上的 N − 1 \rm N-1 N−1 个盘子移动到 A【即以 CBA 顺序执行 N − 1 \rm N-1 N−1 阶汉诺塔】
- 把 N \rm N N 号盘子从 B 移动到C
- 把 A 柱上的 N − 1 \rm N-1 N−1 个盘子移动到 C 柱上【即以 ABC 顺序执行 N − 1 \rm N-1 N−1 阶汉诺塔】
所以核心代码与经典汉诺塔类似,书中给出的思路和代码有点繁琐了
public static void process(Stack<Integer> A, Stack<Integer> B, Stack<Integer> C, int N) {
if (N == 0) return;
process(A, B, C, N - 1);
B.push(A.pop());
process(C, B, A, N - 1);
C.push(B.pop());
process(A, B, C, N - 1);
}
题目链接:https://www.nowcoder.com/ta/programmer-code-interview-guide 【用栈来求解汉诺塔问题】
import java.util.Scanner;
import java.util.Stack;
public class Main {
public static Stack<Integer> A = new Stack<>(), B = new Stack<>(), C = new Stack<>();
public static int counter = 0;
public static void main(String[] args) {
Scanner S = new Scanner(System.in);
int N = S.nextInt();
while (N > 0) {
A.push(N--);
}
process(A, B, C, A.size());
System.out.printf("It will move %d steps.", counter);
}
public static void process(Stack<Integer> A, Stack<Integer> B, Stack<Integer> C, int N) {
if (N == 0) return;
process(A, B, C, N - 1);
counter++;
System.out.printf("Move %d from %s to %s\n", A.peek(), getString(A), getString(B));
B.push(A.pop());
process(C, B, A, N - 1);
counter++;
System.out.printf("Move %d from %s to %s\n", B.peek(), getString(B), getString(C));
C.push(B.pop());
process(A, B, C, N - 1);
}
public static String getString(Stack<Integer> S) {
if (S == A) return "left";
else if (S == B) return "mid";
return "right";
}
}