如果有的题目要求不能用递归,必须借助栈来完成,那么情况就会稍显复杂
如果递归是返回一个值,那么我们可以通过动态规划来求解,如果是递归进行一些操作,比如汉诺塔,那么我们只能用栈模拟这个过程
比如题目【面试题 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