每日一题包含七大板块,现在从最基本的算法题开始,此类题型包括位运算、递推、递归、二分、排序、贪心等,从简单到复杂,跟我一起从点滴积累,到最终一举成名,打遍天下!✨
💻基本算法题9. 奇怪的汉诺塔
汉诺塔问题,条件如下:
1、这里有 A、B、C 和 D 四座塔。
2、这里有 n 个圆盘,n 的数量是恒定的。
3、每个圆盘的尺寸都不相同。
4、所有的圆盘在开始时都堆叠在塔 A 上,且圆盘尺寸从塔顶到塔底逐渐增大。
5、我们需要将所有的圆盘都从塔 A 转移到塔 D 上。
6、每次可以移动一个圆盘,当塔为空塔或者塔顶圆盘尺寸大于被移动圆盘时,可将圆盘移至这座塔上。
请你求出将所有圆盘从塔 A 移动到塔 D,所需的最小移动次数是多少。
输入格式
没有输入
输出格式
对于每一个整数 n,输出一个满足条件的最小移动次数,每个结果占一行。
数据范围
1≤n≤12
输入样例:
没有输入
输出样例:
参考输出格式
🍀题解 — DP+递推
对于汉诺塔问题,我们究其根底
约定:
- 我们用ABCDE来命名塔的名字,
- 默认初始的圆盘都在A塔上,最终要移动到最后一个塔上。
- d 数组存放3个塔的结果,f 数组存放4个塔的结果, g 数组存放5个塔的结果
两个塔(AB)
如果只有两个塔,那么只有圆盘个数为一时,答案为1,其余答案为0
三个塔(ABC)
如果有三个塔,移动时我们需要借助二塔里的结论
如果要移动 n 个圆盘,我们需要在三塔的情况下,将上面 n - 1 个较小圆盘移动到B塔,在将一个大圆盘移动到C塔,最后在将 n - 1 个圆盘在三塔的情况下从B移动到C塔
结论:d(n) = d(n - 1) + 1 + d(n - 1)
四个塔(ABCD)
解决完三塔问题,四塔问题将于三塔类似
如果要移动n个圆盘从A到D的话,我们的解决方法是将一部分圆盘(i个圆盘)先移动到B(或者C),因为这前几个圆盘都是非常小的,后面剩下的大圆盘不能压在上面,所以放小圆盘这个塔暂时不能使用
那么问题将会变成,怎么在三塔情况下将n - i 个圆盘从A移动到D,这时可以用三塔的结论;
问题1:我们先移动的一部分圆盘到底是几个呢,我们需要枚举,最终答案取最小值。
结论:先把前 i 个盘子在四塔情况下移动到B,剩下的盘子在三塔情况下移动到D,最后在将那 i 个小的盘子在四塔情况下从B移动到D。
f(n) = min(f(n), f(i) + d(n - i) + f(i)) 0 <= i <= n;
对于这道题:
首先,可以初步确定,这是一道递归/递推的题目。
我们先考虑三个塔的汉诺塔问题,最优秀方案:必然是先挪走n-1
个圆盘,然后再挪走圆盘N,
因此可以得出递推方程也就是 d[i]=d[i-1]*2+1
;
之所以要乘以2,是因为第一次挪到第二个塔,然后还要挪移回到第三个塔,下面四个塔也是这样的
接着考虑四塔问题,我们可以这么思考,首先挪走j个塔,也就是有四个塔可以选择,然后再挪走剩下的n-j个塔,此时有三个塔可以选择,因此这就是我们的状态转移方程:
f[i]=min(f[i],f[j]*2+d[n-j])
;其中i
表示当前一共有几个塔,也就是上文所说的n
📝代码展示
#include <bits/stdc++.h>
using namespace std;
int d[21],f[21],i,j;
int main()
{
for (i=1;i<=12;i++)
d[i]=2*d[i-1]+1;
memset(f,0x3f,sizeof(f));
f[0]=0;
for (i=1;i<=12;i++)
for (j=0;j<i;j++)
f[i]=min(f[i],f[j]+f[j]+d[i-j]);
for (i=1;i<=12;i++)
cout<<f[i]<<endl;
}
【🎯知识点:递推算法】
递推 是指将一个看似复杂,难以求解的问题一步步的转换成小问题,最后到达一个(或若干个)基本解,这些基本解很好想出来,也有的是人为规定的,然后再依赖这些基本解一步一步反向求解最初的大问题的过程。
举一个最简单和常见的例子,斐波那契数列,这个数列的每一项等于前面两项之和。如下:
{1,1,2,3,5,8,13…}
如果让你直接求斐波那契数列的第100个数f(100),很难直接想出来,但是我们可以通过找到如下的递推关系:f(n)=f(n−1)+f(n−2)
,并且确定基本解f(1)=1
,f(2)=1
(因为递推关系只依赖前面两项,所以只求出基本的两项即可),之后就可以求出f(3),再用f(2)+f(3)
求f(4),最后求解到f(100)。
很多问题可以找到递推关系,所以这个工具很常用,比如解决具有重叠子问题的递推关系的算法叫“动态规划”,它出现在很多面试题和算法竞赛当中 。