LeetCode 332. Reconstruct Itinerary 最小歐拉路徑

題意

給N個單詞表示N個點,和N-1個單詞對,表示可以走的路徑,求字典序最小的總路徑。

 

首先說下這麼暴力DFS能過。暴力的我都不敢寫= =

class Solution {
public:
    vector<string> findItinerary(vector<vector<string> >& tickets) {
        map<string, vector<string> > mp;

        for (int i = 0; i < tickets.size(); i++) {
            string from = tickets[i][0];
            string to = tickets[i][1];
            if (mp.find(from) == mp.end()) {
                vector<string> v;
                v.push_back(to);
                mp[from] = v;
            } else {
                mp[from].push_back(to);
            }
        }
        for (map<string, vector<string> >::iterator iter = mp.begin(); iter != mp.end(); iter++) {
            sort(iter->second.begin(), iter->second.end());
        }
        vector<string> res;
        string cur = "JFK";
        res.push_back(cur);
        dfs(cur, mp, res, tickets.size());
        return res;
    }

    bool dfs(string cur, map<string, vector<string> > &mp, vector<string> &res, int n) {
        if (res.size() == n + 1) return true;
        if (mp.find(cur) == mp.end()) return false;
        if (mp[cur].size() == 0) return false;
        for (int i = 0; i < mp[cur].size(); i++) {
            string nxt = mp[cur][i];
            res.push_back(nxt);
            mp[cur].erase(mp[cur].begin() + i);
            if (dfs(nxt, mp, res, n)) return true;
            mp[cur].insert(mp[cur].begin() + i, nxt);
            res.pop_back();
        }
        return false;
    }
};
View Code

 

然後說正解。

如果把每一個字符串當做一個點,每一個字符串對就是一條有向邊。那麼這麼題目就是要求輸出最小字典序的歐拉路徑。

 

以下參考 https://www.cnblogs.com/TEoS/p/11376707.html

什麼是歐拉路徑?歐拉路徑就是一條能夠不重不漏地經過圖上的每一條邊的路徑,即小學奧數中的一筆畫問題。而若這條路徑的起點和終點相同,則將這條路徑稱爲歐拉回路。

如何判斷一個圖是否有歐拉路徑呢?顯然,與一筆畫問題相同,一個圖有歐拉路徑需要以下幾個條件:

  • 首先,這是一個連通圖
  • 若是無向圖,則這個圖的度數爲奇數的點的個數必須是0或2;若是有向圖,則要麼所有點的入度和出度相等,要麼有且只有兩個點的入度分別比出度大1和少1

上面這兩個條件很好證明。查找歐拉路徑前,必須先保證該圖滿足以上兩個條件,否則直接判誤即可。

查找歐拉路徑的算法有Fluery算法和Hierholzer算法。下面介紹一下Hierholzer算法。

算法流程:

  1. 對於無向圖,判斷度數爲奇數的點的個數,若爲0,則設任意一點爲起點,若爲2,則從這2個點中任取一個作爲起點;對於有向圖,判斷入度和出度不同的點的個數,若爲0,則設任意一點爲起點,若爲2,則設入度比出度小1的點爲起點,另一點爲終點。具體起點的選擇要視題目要求而定。
  2. 從起點開始進行遞歸:對於當前節點x,掃描與x相連的所有邊,當掃描到一條(x,y)時,刪除該邊,並遞歸y。掃描完所有邊後,將x加入答案隊列。
  3. 倒序輸出答案隊列。(因爲這裏是倒序輸出,我們可以用棧來存儲答案,當然用雙端隊列也可以)

 

我畫圖理解一下這個算法,一個歐拉路徑其實都是這個樣子的

就是從起點到終點的路徑上畫幾個圈。

舉兩個具體的例子

 

path = []

A --> B --> C 因爲C沒有再相連的邊 所以把C加入路徑 path=[C]

    --> D --> B 因爲B沒有再相連的邊 所以把B加入路徑 path=[C, B]

      D  path=[C, B, D]

   B path=[C, B, D, B]

A path=[C, B, D, B, A]

 

path = []

A --> B --> C --> B --> D 因爲D沒有再相連的邊 所以把D加入路徑 path=[D]

          B path=[D, B]

      C path=[D, B, C]

   B path=[D, B, C, B]

A path=[D, B, C, B, A]

 

所以無論先遍歷的那一條邊都能得出正確的歐拉路徑,既然題目要求字典序,那麼每次選擇最小字符串先處理即可。

 

代碼

class Solution {
public:
    vector<string> findItinerary(vector<vector<string> >& tickets) {
        map<string, priority_queue<string,vector<string>,greater<string> > > mp;

        for (int i = 0; i < tickets.size(); i++) {
            string from = tickets[i][0];
            string to = tickets[i][1];
            if (mp.find(from) == mp.end()) {
                priority_queue<string,vector<string>,greater<string> > q;
                q.push(to);
                mp[from] = q;
            } else {
                mp[from].push(to);
            }
        }
        vector<string> res;
        string cur = "JFK";
        dfs(cur, mp, res);
        reverse(res.begin(), res.end());
        return res;
    }

    void dfs(string cur, map<string, priority_queue<string,vector<string>,greater<string> > > &mp, vector<string> &res) {
        while(mp[cur].size()) {
            string nxt = mp[cur].top();
            mp[cur].pop();
            dfs(nxt, mp, res);
        }
        res.push_back(cur);
    }
};

 

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