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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章