LeetCode:面试题08.06汉诺塔 用栈模拟递归过程的一些总结

如果有的题目要求不能用递归,必须借助栈来完成,那么情况就会稍显复杂

如果递归是返回一个值,那么我们可以通过动态规划来求解,如果是递归进行一些操作,比如汉诺塔,那么我们只能用栈模拟这个过程

比如题目【面试题 08.06. 汉诺塔问题

在经典汉诺塔问题中,有 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个。

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/hanota-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

假如A上有n个圆盘,对于一次递归的操作,我们是这样的:

  • 借助C柱将A柱上的n-1个圆盘移动到B柱子上(递归
  • 将A柱子最底部的圆盘移动到C柱子上
  • 借助A柱将B柱上的n-1个圆盘移动到C柱子上(递归

我们可以快速地写出递归的伪代码:

// A是源柱子,B是借助的柱子,C是目的柱子
def hanoTower(A, B, C, n)
	hanoTower(A, C, B, n-1);
	move A -> C
	hanoTower(B, A, C, n-1);

栈中的元素是什么?

栈保存的是递归时的状态,那么栈中的元素应该是递归函数的所有参数,也就是我们要保存四个参数A,B,C,n

但是注意到这题,递归函数的调用不是在最后,意味着递归结束之后还要进行操作,那么我们还要额外存储一个状态标志位satate,来标志递归进行到哪里了

X节点出现在栈顶一次,我们就说它被访问一次,通过访问了几次,来判断一趟递归,进行到第几条语句

在这里插入图片描述

代码

class Solution {
public:
    typedef struct p
    {
        vector<int> *sorce, *helper, *target; int n, state;
        p(vector<int>* S, vector<int>* H, vector<int>* T, int N):
        sorce(S),helper(H),target(T),n(N),state(0){}
    }p;
    void hanota(vector<int>& A, vector<int>& B, vector<int>& C)
    {
        if(A.size()<2) {C=A; A={}; return;}
        stack<p> s;
        s.push(p(&A, &B, &C, A.size()));
        while(!s.empty())
        {
            p tp=s.top();
            // 边界情况
            if(tp.n==2)
            {
                tp.helper->push_back(tp.sorce->back());
                tp.sorce->pop_back();
                tp.target->push_back(tp.sorce->back());
                tp.sorce->pop_back();
                tp.target->push_back(tp.helper->back());
                tp.helper->pop_back();
                s.pop();
                continue;
            }
            // 第一次访问,借助C柱将A柱上的n-1个圆盘移动到B柱子上(递归
            if(tp.state==0)
            {
                s.top().state++;
                s.push(p(tp.sorce, tp.target, tp.helper, tp.n-1));
            }
            // 第二次访问将A柱子最底部的圆盘移动到C柱子上
            // 借助A柱将B柱上的n-1个圆盘移动到C柱子上(递归
            else if(tp.state==1)
            {
                tp.target->push_back(tp.sorce->back());
                tp.sorce->pop_back();
                s.top().state++;
                s.push(p(tp.helper, tp.sorce, tp.target, tp.n-1));
            }
            // 第三次访问,说明操作都做完了,退栈
            else if(tp.state==2) s.pop();
        }
    }
};

总结:

如果递归的语句是在一个递归函数的最后被执行的,那么我们可以不用记录第几次被访问

但是如果一趟递归里面,要进行多次子递归,并且这些子递归之间夹杂不同的操作语句,那么需要根据节点是第几次被访问,来判断要进行那些操作,需要设置标志位state

发布了238 篇原创文章 · 获赞 7 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_44176696/article/details/104897188
今日推荐