題目描述
Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from start to end, such that:
Only one letter can be changed at a time
Each intermediate word must exist in the dictionaryFor example,
Given:
start = “hit”
end = “cog”
dict = [“hot”,”dot”,”dog”,”lot”,”log”]Return
[ ["hit","hot","dot","dog","cog"], ["hit","hot","lot","log","cog"] ]
解題思路
- wordladder2 是wordladder的延伸,wordladder是給定起始單詞和字典,然後尋找一條最短路徑也可能沒有此路徑,wordladder2是找到所有的最短路徑。
- 尋找最短路徑利用廣度優先的策略,也就是BFS搜素,與其對應的是深度優先搜索DFS,這裏廣優先利用輔助隊列來實現,通常對圖、樹的數據結構進行BFS和DFS。
- wordladder只需尋找最短路徑數值,我們不需要記錄路徑。當然wordladder實現的時候需要注意利用題目條件,26個小寫字母的限制,而非傳統比較字符串判斷是否可達,這樣才能滿足題目要求的複雜度。
- wordladder2需要增加路徑記錄,我們在找最短路徑數值的時候每加入到隊列中一個點我們會在字典中把這個點刪去,這樣不影響最短路徑數值的記錄,但是我們需要求解所有最短路徑就不能這樣做了,我們需要將每一層(廣度優先可以理解爲一層一層深入)的節點全部加入的時候纔可以將這些節點從集合中一次性刪除,這裏我們對節點進行了封裝,需要記錄其前驅節點,同時要記錄該節點所處的層次,這樣我們才知道當前節點所處層次信息以及某一次層次的遍歷情況。
- 這裏我第一次超時了,後來進行修正,添加了一個記錄當前層次遍歷過的節點隊列,在加入隊列的時候
如果是這些節點數值則跳過不加入,按照先前的思路這些節點會被加入,但是其層次屬性會加1,實際上隊列中遍歷到這些節點的時候,這些節點的數值已經從字典中刪除了,會直接略過,如果畫圖的話會發現這是一些迴路情況,這樣許多重複節點(數值一樣,層次不同)加入隊列,很影響效率然後我就用存儲當前層次所處理(從隊列中彈出並對可達節點分析)過的節點,這樣就會將一些迴路節點跳過,一定程度上提高效率,然後AC通過。 - 計算路徑的時候注意list的插入操作,LinkedList頭結點插入,可以在最短時間內得到路徑,如果適用ArrayList估計就TLE了。
詳細代碼
public class Solution {
public List<List<String>> findLadders(String start, String end, Set<String> dict) {
List<List<String>> resultLists = new ArrayList<>();//結果
dict.add(start);//頭部加入
dict.add(end);//尾部加入
Queue<NodeForLadder> queue = new LinkedList<NodeForLadder>();
queue.add(new NodeForLadder(start, 1, null));//入隊 深度1 父節點null
Set<String> visitedPerDeepList = new HashSet();//記錄當前層次遍歷過的節點
int currentDeep = 1; //初始化當前深度1
Boolean hasFound = false;
while(!queue.isEmpty())
{
NodeForLadder current = queue.poll();
if(current.deep != currentDeep)
{
//已經進入到下一個層次,將原先層次所有的節點先刪除掉
dict.removeAll(visitedPerDeepList);
//更新層次信息
currentDeep = current.deep;
if(hasFound)
{
break;//已經找到最短路徑,再次更新層次進入下一層,說明上一層遍歷完畢,最短路徑們都已走過
}
visitedPerDeepList.clear();//已經遍歷的層次節點清空
}
visitedPerDeepList.add(current.string);//加入遍歷過的層次節點
if(current.string.equals(end))
{
//找到最短路徑 添加
resultLists.add(findLadderPath(current));
hasFound = true; // 抵達最短層
}
else
{
//取點 找臨街的表,這裏藉助最大固定數目的變數來計算而非常規利用矩陣
String tmp = current.string;
//首先判斷是否還有此點,以爲層次更新時會刪除上一層節點,這裏排除迴路的情況
if(!dict.contains(tmp))
{
continue;
}
for(int i=0;i<tmp.length();i++)
{
for(char c='a';c<='z';c++)
{
if(c==tmp.charAt(i))
{
continue;
}
else
{
char cc[] = tmp.toCharArray();
cc[i] = c;
String s = new String(cc);
if(dict.contains(s) && !visitedPerDeepList.contains(s))//該層的結點訪問過的不要再加入隊列
{
queue.add(new NodeForLadder(s, current.deep+1, current));
// dict.remove(s);//這裏還不能remove 和找最短路徑數值不同
}
}
}
}
}
}
return resultLists;
}
class NodeForLadder
{
String string;
int deep;
NodeForLadder parent;//父節點
public NodeForLadder(String string , int deep, NodeForLadder p)
{
this.string = string;
this.deep = deep;
this.parent = p;
}
}
//從根節點依次遍歷父節點找到路徑 利用鏈表頭部插入操作
List<String> findLadderPath(NodeForLadder n)
{
List<String> pathList = new LinkedList<>();
while(n != null)
{
pathList.add(0, n.string);
n = n.parent;
}
return pathList;
}
}
總結
代碼中的註釋就是按照上述思路來的,此題屬於AC率較低的(前五),尤其適用java來做的話對算法的要求更高,需要熟悉java集合的使用,比如頭結點插入操作LinkedList效率高於ArrayList,判斷集合中是否含有某一元素的話Set(O(1))效率高於LinkedList(O(n)),許多細節需要注意,總體的思路要正確(利用26小寫字母)以及具體編碼實現(集合選取,前驅記錄,visitedPerDeepList記錄等等)