LeetCode:Word Ladder I II


其他LeetCode題目歡迎訪問:LeetCode結題報告索引

LeetCode:Word Ladder 

Given two words (start and end), and a dictionary, find the length of shortest transformation sequence from start to end, such that:

  1. Only one letter can be changed at a time
  2. Each intermediate word must exist in the dictionary

For example,

Given:
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]

As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
return its length 5.

Note:

    • Return 0 if there is no such transformation sequence.
    • All words have the same length.
    • All words contain only lowercase alphabetic characters.

分析:這種題,肯定是每次改變單詞的一個字母,然后逐漸搜索,很多人一開始就想到用dfs,其實像這種求最短路徑、樹最小深度問題bfs最適合,可以參考我的這篇博客bfs(層序遍歷)求二叉樹的最小深度。本題bfs要注意的問題:

  • 和當前單詞相鄰的單詞是:對當前單詞改變一個字母且在字典中存在的單詞
  • 找到一個單詞的相鄰單詞,加入bfs隊列后,要從字典中刪除,因為不刪除的話會造成類似於hog->hot->hog的死循環。而刪除對求最短路徑沒有影響,因為我們第一次找到該單詞肯定是最短路徑,即使后面其他單詞也可能轉化得到它,路徑肯定不會比當前的路徑短(如果要輸出所有最短路徑,則不能立即從字典中刪除,具體見下一題)
  • bfs隊列中用NULL來標識層與層的間隔,每次碰到層的結尾,遍歷深度+1

我們利用和求二叉樹最小深度層序遍歷的方法來進行bfs,代碼如下:                                                                                                                              本文地址

 1 class Solution {
 2 public:
 3     int ladderLength(string start, string end, unordered_set<string> &dict) {
 4         // IMPORTANT: Please reset any member data you declared, as
 5         // the same Solution instance will be reused for each test case.
 6         //BFS遍歷找到的第一個匹配就是最短轉換,空字符串是層與層之間的分隔標志
 7         queue<string> Q;
 8         Q.push(start); Q.push("");
 9         int res = 1;
10         while(Q.empty() == false)
11         {
12             string str = Q.front();
13             Q.pop();
14             if(str != "")
15             {
16                 int strLen = str.length();
17                 for(int i = 0; i < strLen; i++)
18                 {
19                     char tmp = str[i];
20                     for(char c = 'a'; c <= 'z'; c++)
21                     {
22                         if(c == tmp)continue;
23                         str[i] = c;
24                         if(str == end)return res+1;
25                         if(dict.find(str) != dict.end())
26                         {
27                             Q.push(str);
28                             dict.erase(str);
29                         }
30                     }
31                     str[i] = tmp;
32                 }
33             }
34             else if(Q.empty() == false)
35             {//到達當前層的結尾,並且不是最后一層的結尾
36                 res++;
37                 Q.push("");
38             }
39         }
40         return 0;
41     }
42 };

 


 

LeetCode:Word Ladder II

Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from start to end, such that:

  1. Only one letter can be changed at a time
  2. Each intermediate word must exist in the dictionary

For example,

Given:
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]

Return

  [
    ["hit","hot","dot","dog","cog"],
    ["hit","hot","lot","log","cog"]
  ]

Note:

  • All words have the same length.
  • All words contain only lowercase alphabetic characters.

分析:本題主要的框架和上一題是一樣,但是還要解決兩個額外的問題:一、 怎樣保證求得所有的最短路徑;二、 怎樣構造這些路徑

第一問題:

  • 不能像上一題第二點注意那樣,找到一個單詞相鄰的單詞后就立馬把它從字典里刪除,因為當前層還有其他單詞可能和該單詞是相鄰的,這也是一條最短路徑,比如hot->hog->dog->dig和hot->dot->dog->dig,找到hog的相鄰dog后不能立馬刪除,因為和hog同一層的單詞dot的相鄰也是dog,兩者均是一條最短路徑。但是為了避免進入死循環,再hog、dot這一層的單詞便利完成后dog還是得從字典中刪除。即等到當前層所有單詞遍歷完后,和他們相鄰且在字典中的單詞要從字典中刪除。
  • 如果像上面那樣沒有立馬刪除相鄰單詞,就有可能把同一個單詞加入bfs隊列中,這樣就會有很多的重復計算(比如上面例子提到的dog就會被2次加入隊列)。因此我們用一個哈希表來保證加入隊列中的單詞不會重復,哈希表在每一層遍歷完清空(代碼中hashtable)。
  • 當某一層的某個單詞轉換可以得到end單詞時,表示已經找到一條最短路徑,那么該單詞的其他轉換就可以跳過。並且遍歷完這一層以后就可以跳出循環,因為再往下遍歷,肯定會超過最短路徑長度

第二個問題:

  • 為了輸出最短路徑,我們就要在比bfs的過程中保存好前驅節點,比如單詞hog通過一次變換可以得到hot,那么hot的前驅節點就包含hog,每個單詞的前驅節點有可能不止一個,那么每個單詞就需要一個數組來保存前驅節點。為了快速查找因此我們使用哈希表來保存所有單詞的前驅路徑,哈希表的key是單詞,value是單詞數組。(代碼中的unordered_map<string,vector<string> >prePath)                         
  • 有了上面的前驅路徑,可以從目標單詞開始遞歸的構造所有最短路徑(代碼中的函數 ConstructResult
 1 class Solution {
 2 public:
 3     typedef unordered_set<string>::iterator HashIter;
 4     vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) {
 5         // Note: The Solution object is instantiated only once and is reused by each test case.
 6         queue<string> Q;
 7         Q.push(start); Q.push("");
 8         bool hasFound = false;
 9         unordered_map<string,vector<string> >prePath;//前驅路徑
10         unordered_set<string> hashtable;//保證bfs時插入隊列的元素不存在重復
11         while(Q.empty() == false)
12         {
13             string str = Q.front(), strCopy = str;
14             Q.pop();
15             if(str != "")
16             {
17                 int strLen = str.length();
18                 for(int i = 0; i < strLen; i++)
19                 {
20                     char tmp = str[i];
21                     for(char c = 'a'; c <= 'z'; c++)
22                     {
23                         if(c == tmp)continue;
24                         str[i] = c;
25                         if(str == end)
26                         {
27                             hasFound = true;
28                             prePath[end].push_back(strCopy);
29                             //找到了一條最短路徑,當前單詞的其它轉換就沒必要
30                             goto END;
31                         }
32                         if(dict.find(str) != dict.end())
33                         {
34                             prePath[str].push_back(strCopy);
35                             //保證bfs時插入隊列的元素不存在重復
36                             if(hashtable.find(str) == hashtable.end())
37                                 {Q.push(str); hashtable.insert(str);}
38                         }
39                     }
40                     str[i] = tmp;
41                 }
42             }
43             else if(Q.empty() == false)//到當前層的結尾,且不是最后一層的結尾
44             {
45                 if(hasFound)break;
46                 //避免進入死循環,把bfs上一層插入隊列的元素從字典中刪除
47                 for(HashIter ite = hashtable.begin(); ite != hashtable.end(); ite++)
48                     dict.erase(*ite);
49                 hashtable.clear();
50                 Q.push("");
51             }
52         END: ;
53         }
54         vector<vector<string> > res;
55         if(prePath.find(end) == prePath.end())return res;
56         vector<string> tmpres;
57         tmpres.push_back(end);
58         ConstructResult(prePath, res, tmpres, start, end);
59         return res;
60     }
61     
62 private:
63     //從前驅路徑中回溯構造path
64     void ConstructResult(unordered_map<string,vector<string> > &prePath, 
65         vector<vector<string> > &res, vector<string> &tmpres, 
66         string &start, string &end)
67     {
68         if(start == end)
69         {
70             reverse(tmpres.begin(), tmpres.end());
71             res.push_back(tmpres);
72             reverse(tmpres.begin(), tmpres.end());
73             return;
74         }
75         vector<string> &pre = prePath[end];
76         for(int i = 0; i < pre.size(); i++)
77         {
78             tmpres.push_back(pre[i]);
79             ConstructResult(prePath, res, tmpres, start, pre[i]);
80             tmpres.pop_back();
81         }
82         
83     }
84 };

另外這一題如果不用隊列來進行bfs,可能會更加方便,使用兩個哈希表來模擬隊列,這樣還可以避免前面提到的同一個元素加入隊列多次的問題,具體可以參考這篇博客

【版權聲明】轉載請注明出處:http://www.cnblogs.com/TenosDoIt/p/3443512.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM