首先透露一下,本人在2018年參加南京大學本科生開放日(保研夏令營)的機試時,此題目爲三道機試題中的一題,十個測試樣例,看你AC了幾個樣例。當時基本沒怎麼準備,看到題目時一臉懵,今天竟然又讓我在LeetCode上碰到了。
題目描述
給定兩個單詞(beginWord
和 endWord
)和一個字典 wordList
,找出所有從 beginWord
到 endWord
的最短轉換序列。轉換需遵循如下規則:
- 每次轉換隻能改變一個字母。
- 轉換過程中的中間單詞必須是字典中的單詞。
說明:
- 如果不存在這樣的轉換序列,返回一個空列表。
- 所有單詞具有相同的長度。
- 所有單詞只由小寫字母組成。
- 字典中不存在重複的單詞。
- 你可以假設
beginWord
和endWord
是非空的,且二者不相同。
示例 1:
輸入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
輸出:
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]
示例 2:
輸入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
輸出: []
解釋: endWord "cog" 不在字典中,所以不存在符合要求的轉換序列。
解題思路
本題目採用廣度優先搜索(BFS)的方法,因此利用隊列來存儲單詞序列。
爲方便起見,我們利用哈希表給單詞標號,操作單詞編號,發現到達終點時再輸出單詞序列。
爲了保留相同長度的多條路徑,我們採用 cost
數組,其中 cost[i]
表示 beginWord
對應的點到第i
個點的代價(即轉換次數)。初始情況下其所有元素初始化爲無窮大。
- 若該節點爲終點,則將其路徑轉換爲對應的單詞存入答案;
- 若該節點不爲終點,則遍歷和它連通的節點(假設爲 next )中滿足
cost[next] >= cost[now] + 1
的加入隊列,並更新cost[next] = cost[now] + 1
。如果cost[next] < cost[now] + 1
,說明這個節點已經被訪問過,不需要再考慮。
以上思路參考:LeetCode官方。
具體實現細節可見代碼及其註釋。
注:
bool checkDifference(string& str1, string& str2)
函數的參數需要加引用,不然會超出時間限制。
具體代碼
class Solution {
public:
//判斷兩個字符串是否相等
bool checkDifference(string& str1, string& str2) {
int difference = 0;
for (int i = 0; i<str1.size() && difference <= 1; i++) {
if (str1[i] != str2[i]) difference++;
}
return difference == 1;
}
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
unordered_map<string, int>wordId;//利用哈希表記錄單詞表中的詞
vector<string>words;//記錄所有的單詞
vector<vector<int>>edge;//記錄每個單詞相鄰的單詞id
int id = 0;//單詞的編號
//利用哈希表記錄單詞
for (const string& word : wordList) {
if (!wordId.count(word)) {
wordId[word] = id++;
words.push_back(word);
}
}
//如果最終的單詞不在wordId中,直接返回{}
if (wordId.count(endWord) == 0) return {};
//如果起始單詞不在wordId中,將其加入
if (wordId.count(beginWord) == 0) {
wordId[beginWord] = id++;
words.push_back(beginWord);
}
//爲edge賦值(找相鄰單詞)
edge.resize(words.size());//重新賦值edge數組大小
for (int i = 0; i<words.size(); i++) {
for (int j = i + 1; j<words.size(); j++) {
if (checkDifference(words[i], words[j])) {
edge[i].push_back(j);
edge[j].push_back(i);
}
}
}
//尋找全部路徑,利用廣度優先搜索
int dest = wordId[endWord];
queue<vector<int>>que;//定義一個隊列用於存放當前的所有路徑
vector<vector<string>>ans;//存放最終的答案
vector<int>cost(words.size(), INT_MAX);//找代價最小的路徑,先賦值爲最大
cost[wordId[beginWord]] = 0;//將起始點的代價設爲0
que.push(vector<int>{wordId[beginWord]});//將起始點放進que
while (que.empty() == 0) {
vector<int>now = que.front();//當前隊列中的遍歷順序
que.pop();
int last = now.back();//當前的隊尾元素
//如果隊尾元素就是終點單詞
if (last == dest) {
vector<string>tmp;
//將now中的id映射成word後放入ans中
for (int i = 0; i<now.size(); i++)
tmp.push_back(words[now[i]]);
ans.push_back(tmp);
}
//如果不是終點單詞還要繼續查找
else {
for (int i = 0; i<edge[last].size(); i++) {
int next = edge[last][i];
//如果下一個相鄰單詞的代價大於當前單詞(保證最短路徑)
if (cost[last] + 1 <= cost[next]) {
cost[next] = cost[last] + 1;
//將新的序列加入到隊列中進行判斷。
vector<int>tmp(now);
tmp.push_back(next);
que.push(tmp);
}
}
}
}
return ans;
}
};