我们所熟知的汉诺塔问题,本质就是在解决重复子问题,因此可以使用递归进行解决。今天在做算法题的时候,发现一道很有意思的汉诺塔变题,给大家分享一下。
题目描述:
Eli最近迷上了汉诺塔。她玩了传统汉诺塔后突发奇想,发明了一种新的汉诺塔玩法。
有A、B、C三个柱子顺时针放置,移动的次序为A仅可以到B,B仅可以到C、C仅可以到A。即只可顺时针移动,不可逆时针移动。当然,汉诺塔的普适规则是适用的:每次移动后,大金片必须在小金片的下面。
现在A柱子上有n个金片。Eli想知道,她把这些全部移动到B或C,分别要多少次操作?
输入描述:一个正整数n
输出描述:两个整数,分别代表A移到B和A移到C的最少操作数。由于该数可能过大,你需要对1000000007取模。
这道题与普通汉诺塔问题的不同之处在于,其柱子的移动次序是固定的,为A->B->C->A,这样的一个循环,而原汉诺塔问题中,一个柱子上的环在满足条件的情况下,是可以放到其余任意一根柱子上的.
所以,在这个题设的限制下,我们要重新思考如何移动,但是应该抓牢一点:这道题虽然是汉诺塔问题的变种,但与汉诺塔问题的核心也许是相似的,因此我们在分析的过程中,也要去尝试寻找重复子问题.
- 一个金片: 从A拿到B为1次,从A拿到C为两次:A->B->C
- 两个金片:
- A->B: 从A拿到B我们需要先将一个金片A->C,再将A中剩下的金片放到B,然后再将C上的金片:C->A->B.整个过程的操作次数为,A->C两次,A->B一次,C->A->B两次,共五次.分析整个过程我们可以发现,除去A->B的一次,剩下的四次对应A->C,C->B,分别各两次—这两种情况实际是相同的,都是指将一块金片从一根柱子拿到与之间隔一根柱子的柱子上,即都可以看成将一块金片从A拿到C.
- A->C: 从A拿到C,我们需要先将一个金片A->C,再将A中剩下的一个金片放到B上,再将C中金片拿到A上,再将B中一个金片放到C上,最后再将A上的金片放到C上.统计一下,总共7次,其中第一次A->C为两次,A->B为一次,C->A为一次,B->C为一次,最后一次A->C是两次.对于A->B和C->A,这两种情况实质是一样的,都是把金片从一根柱子放到另一根与之相邻的柱子上,因此我们就操作次数而言,可以把这两种情况都统一成A->B.
经过上述的分析,我们可以发现,这个汉诺塔问题的变种中,确实存在解决重复子问题.
我们以A柱子上有n个金片,推而广之,来讲一下整体的思路:
如果n为1,那么A->B是一次,A->C为两次.
如果n大于1,对于A->B而言:
- n-1个金片,从A到C
- 1个金片,从A->B
- n - 1个金片,从C到B,就次数而言,等价于n-1个金片,从A到C
如果n大于1,对于A->C而言:
- n - 1个金片,A->C
- 1个金片,A->B
- n - 1个金片,C->A,就次数而言,等价于n - 1个金片,A->B
- 1个金片B->C
- n - 1个金片,A->C
我们可以很明显地感受到,操作n个金片的次数,依赖于操作n - 1个金片的次数,我们可以使用动态规划进行解决,而且由于当前状态仅依赖于前一个状态,我们还可以使用滚动数组进行优化,使得空间复杂度为O(1).动态规划中的相应状态变量应设置为两个:对于n个金片,从A->B和从A->C分别对应的操作次数.
动态规划迭代解法如下:
#include <iostream>
using namespace std;
const int MOD = 1e9 + 7;
int n;
int main() {
cin >> n;
long x = 1,y = 2;
for(int i = 2;i <= n;i++)
{
long xx = x,yy = y;
x = (2 * yy % MOD + 1) % MOD;
y = (2 + 2 * yy % MOD + xx) % MOD;
}
cout << x << ' ' << y << endl;
return 0;
}
当然,动态规划迭代求解类的题目,即解决重复子问题,很多情况下都可以转化为递归进行求解,但是一般而言不会去进行这样的转化,因为递归的时间与空间复杂度往往会更大.
这道题也一样可以进行转化,但是由于本题的两个状态变量之间互相牵扯,进行递归的递归重复度回过高,因此即便算法思路正确,对于n较大的情况,会出现超时.
递归解法如下:
#include <iostream>
using namespace std;
const int MOD = 1e9 + 7;
int n;
long dfsAC(int n);
long dfsAB(int n)
{
if(n == 1)
return 1;
return (2 * dfsAC(n - 1) % MOD + 1) % MOD;
}
long dfsAC(int n)
{
if(n == 1)
return 2;
return (2 + 2 * dfsAC(n - 1) % MOD + dfsAB(n - 1)) % MOD;
}
int main() {
cin >> n;
cout << dfsAB(n) << ' ' << dfsAC(n) << endl;
return 0;
}
1993

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



