一.题目
面试题 08.06. 汉诺塔问题 - 力扣(LeetCode)

二.思路讲解
2.1 思路讲解
现在我们理解递归,就采取宏观理解递归 的方法来解决这题。首先,用上节课说的从小方面找规律,帮助我们理解大问题如何拆成小问题!
-
当只有一个盘子的情况下,我们是不是可以直接把 A 上的盘子直接移到 C 里面即可。
-
当有两个盘子的情况下,我们是不是需要先把 A 的第一个盘子移到 B ,然后再把第二个盘子从 A 移到 C ,最后再把 B 的盘子移到 C。
-
当有三个盘子的情况下,我们要想把第三个盘子移到 C,那么是不是先把第一和第二个盘子移到 B,然后就能把第三个盘子移到 C,最后再把 B 的两个盘子移到 C。
但是,怎么能移动两个盘子呢?直接移不合法。因此我们要把移动两个盘子变成合法的,这不就回到了只有两个盘子的情况 吗?只不过这次是让盘子从 A 移到 B,不再是 A 移到 C,因此我们的目标柱变成了 B ,而 C 变成了中转站(辅助柱)。
那如果有四个盘子,不也是先把前三个盘子移到 B,然后最后一个移到 C,接着处理三个盘子------这不就又回到了只有三个盘子的情况吗?如此层层递进,就形成了递归的规律。
三.代码演示
cpp
class Solution {
public:
void hanota(vector<int>& A, vector<int>& B, vector<int>& C)
{
dfs(A,B,C,A.size());
}
void dfs(vector<int>& A, vector<int>& B, vector<int>& C,int n)
{
//递归终止条件
if(n == 1)
{
C.push_back(A.back());
A.pop_back();
return;
}
//1.把A中n-1个盘子移到B柱借用C柱子
dfs(A,C,B,n - 1);
//2.把A中第n个盘子移动到C柱子
C.push_back(A.back());
A.pop_back();
//3.把B中n-1个盘子移动到C柱子借用A柱子
dfs(B,A,C,n-1);
}
};
四.代码讲解
一、递归函数设计
我们定义一个递归函数 dfs(A, B, C, n) ,它的含义是:将柱子 A 上的 n 个盘子,借助辅助柱子 B ,全部移动到目标柱子 C 上。这里 A、B、C 分别对应三个柱子的数组引用,直接操作原数组。
二、递归终止条件
当 n == 1 时,说明只剩下一个盘子需要移动。此时,直接将 A 的最后一个盘子(即栈顶)取出,放入 C 中,然后从 A 中移除该盘子。这个操作是直接可行 的,也是递归的最小子问题。
三、递归步骤分解
对于 n > 1 的情况,我们将问题分解为三个子步骤,这正是汉诺塔问题的核心规律:
-
将 A 上的 n-1 个盘子借助 C 移动到 B 调用
dfs(A, C, B, n - 1)。这一步的目标是把除了最底下那个最大的盘子之外的所有盘子,从 A 移到 B,期间可以借助 C 作为辅助。此时,B 成为这些盘子的目标柱,C 是辅助柱。 -
将 A 上剩下的第 n 个盘子直接移动到 C 此时 A 上只剩下最大的那个盘子,直接将其从 A 的末尾取出,放入 C 的末尾,并从 A 中删除。这个操作是单步移动,无需借助其他柱子。
-
将 B 上的 n-1 个盘子借助 A 移动到 C 调用
dfs(B, A, C, n - 1)。现在,所有较小的盘子都在 B 上,我们需要把它们再移到 C 上,此时可以借助 A 作为辅助柱。这一步完成后,所有盘子都按顺序移到了 C。
四、关键细节
-
宏观理解 :我们不必纠结于每一步具体如何移动,只需相信递归函数能完成它的任务 。例如,
dfs(A, C, B, n-1)一定能把 A 上的 n-1 个盘子正确移到 B,至于它内部怎么做的,我们暂时不用管。 -
参数顺序 :递归调用时,柱子的角色会发生变化。注意每次调用时,三个参数的顺序对应着源柱、辅助柱、目标柱 。例如
dfs(A, C, B, n-1)表示从 A 移到 B,C 是辅助。 -
数组操作 :这里用
push_back和pop_back模拟盘子的取出和放入,每个柱子就是一个栈,后进先出,符合汉诺塔规则。