如果有的題目要求不能用遞歸,必須藉助棧來完成,那麼情況就會稍顯複雜
如果遞歸是返回一個值,那麼我們可以通過動態規劃來求解,如果是遞歸進行一些操作,比如漢諾塔,那麼我們只能用棧模擬這個過程
比如題目【面試題 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