【递归】1.汉诺塔问题

cpp 复制代码
面试题 08.06. 汉诺塔问题
leetcode链接:https://leetcode.cn/problems/hanota-lcci/description/

在经典汉诺塔问题中,有 3 根柱子及 N 个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。
一开始,所有盘子自上而下按升序依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。
移动圆盘时受到以下限制:
(1) 每次只能移动一个盘子;
(2) 盘子只能从柱子顶端滑出移到下一根柱子;
(3) 盘子只能叠在比它大的盘子上。

请编写程序,用栈将所有盘子从第一根柱子移到最后一根柱子。

你需要原地修改栈。

示例1:

 输入:A = [2, 1, 0], B = [], C = []
 输出:C = [2, 1, 0]
示例2:

 输入:A = [1, 0], B = [], C = []
 输出:C = [1, 0]
提示:

A中盘子的数目不大于14个。

本文章主要围绕两个问题来展开:
1.为什么汉诺塔问题能使用递归?
2.如何解决汉诺塔问题?

++为什么汉诺塔问题能使用递归?++

可以使用递归的题目,可以总结为一句话:
解决大问题,可以使用相同类型的子问题

解决子问题 ,可以使用相同类型的子问题

我们来对汉诺塔问题进行分析:
当N = 1时,移动的策略:

直接将A中的盘子转移到C中

当N = 2时,移动的策略:

1.将A上面的第一个盘子移动到B上,

2.将A上面的第二个盘子移动到C上

3.将B上面的盘子移动到C上

当N = 3时,移动的策略:
1.将A上面的第一个盘子移动到C上
2.将A上面的第二个盘子移动到B上
3.将C上面的第一个盘子移动到B上

4.将A上面的第一个盘子移动到C上

5.将B上面的第一个盘子移动到A上
6.将B上面的第二个盘子移动到C上
7.将A上面的第一个盘子移动到C上

通过上面的演示,可以得到什么?

下面的三步
1.将A上面的第一个盘子移动到C上
2.将A上面的第二个盘子移动到B上
3.将C上面的第一个盘子移动到B上

N = 3时,前面的三步就是将A中的N-1个盘子借助C移动到B上,而这个操作,就是和N = 2时一模一样的。

而下面的这三步,
5.将B上面的第一个盘子移动到A上
6.将B上面的第二个盘子移动到C上
7.将A上面的第一个盘子移动到C上

N = 3时,这三步就是将B中的N-1个盘子借助A移动到C上,而这个操作,就是和N = 2时一模一样的。

++因此,本问题的流程就抽象成什么了?++

当解决N = 3时,可以用N = 2的策略 --> 当解决N个盘子的问题时,可以使用N - 1个盘子的策略。

++而我前面提到的递归的本质:++
解决大问题,可以使用相同类型的子问题

解决子问题 ,可以使用相同类型的子问题

++如何写递归的代码??++

写递归的代码,需要三个部分:

cpp 复制代码
1.挖掘出重复的子问题  --> 函数头

2.只关心某一个子问题在做什么  --> 函数体

3.递归的出口 --> 防止死递归

1.挖掘出重复的子问题 --> 函数头

根据前面提到的n = 3时如何转化子问题的内容,将流程抽象出来,就变成了:

N = 3时,

cpp 复制代码
第一步:将A中的N - 1个盘子借助C转移到B上 (重复的子问题)
第二步:将A中的最后一个盘子转移到C上
第三步: 将B中的N - 1个盘子借助A转移到C上 (重复的子问题)

我们根据重复的子问题设计出函数头。

从上面可以发现,有4个参数,分别是A,B,C和盘子的数量N

因此,函数头设计为:

2.只关心某一个子问题在做什么 --> 函数体

N = 3时,

cpp 复制代码
第一步:将A中的N - 1个盘子借助C转移到B上 (重复的子问题)
第二步:将A中的最后一个盘子转移到C上
第三步: 将B中的N - 1个盘子借助A转移到C上 (重复的子问题)

我们要从宏观去思考,不要思考dfs如何实现这个任务,把自己当成领导,相信dfs一定能完成你布置的任务。

cpp 复制代码
给dfs布置要给任务,相信dfs一定能完成任务 。

3.递归的出口 --> 防止死递归

n = 1时,不需要借助其他盘子,直接将A上的盘子转移到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)
        {
            //直接将a中的盘子转移到c上
            c.push_back(a.back());
            a.pop_back();
            return;
        }

        //当有n个盘子的时候
        dfs(a, c, b, n - 1);   //将a中的n-1个盘子,借助c,转移到b上

        //将a中的最后一个盘子转移到c上
        c.push_back(a.back());
        a.pop_back();

        //将b中的n-1个盘子,借助a,转移到c上
        dfs(b, a, c, n - 1);
    }
};

总结

++1.什么问题能使用递归?++
解决大问题,可以使用相同类型的子问题

解决子问题 ,可以使用相同类型的子问题
++2.如果要写递归,需要哪几步?++

cpp 复制代码
第一步:挖掘出相同的子问题  (关系到具体函数头的设计
第二步:只关心具体子问题做了什么  (关系到具体函数体怎么写,是一个宏观的过程)
第三步:找到递归的出口,防止死递归  (关系到如何跳出递归)
相关推荐
DolphinDB7 分钟前
如何在C++交易系统中集成高性能回测与模拟撮合
c++
筏.k31 分钟前
C++ 网络编程(14) asio多线程模型IOThreadPool
网络·c++·架构
爱喝茶的小茶2 小时前
周赛98补题
开发语言·c++·算法
OpenC++2 小时前
【C++】备忘录模式
c++·设计模式·备忘录模式
小庞在加油3 小时前
《dlib库中的聚类》算法详解:从原理到实践
c++·算法·机器学习·数据挖掘·聚类
ComputerInBook3 小时前
C++ 标准模板库算法之 transform 用法
开发语言·c++·算法·transform算法
2301_803554526 小时前
c++中类的前置声明
java·开发语言·c++
hn小菜鸡9 小时前
LeetCode 377.组合总和IV
数据结构·算法·leetcode
LyaJpunov9 天前
深入理解 C++ volatile 与 atomic:五大用法解析 + 六大高频考点
c++·面试·volatile·atomic
小灰灰搞电子9 天前
Qt PyQt与PySide技术-C++库的Python绑定
c++·qt·pyqt