[LeetCode#128]Word Ladder II
Problem:
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 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.
Analysis:
As we have analyzed in the Word Ladder simple version, the out-degree for each word is 26*word_len. Thus we should not be affected by any previous question, try to replace "character" one index by one index. Below is a wrong solution through such wrong thinking.
Wrong solution 1:
public class Solution {
public List<List<String>> findLadders(String start, String end, Set<String> dict) {
List<List<String>> ret = new ArrayList<List<String>> ();
if (start == null || end == null || dict == null)
throw new IllegalArgumentException("The passed in arguments is illegal");
ArrayList<String> path = new ArrayList<String> ();
path.add(start);
findPath(start, end, 0, dict, path, ret);
return ret;
}
private void findPath(String cur_str, String end, int index, Set<String> dict, ArrayList<String> path, List<List<String>> ret) {
if (cur_str.equals(end)) {
ret.add(new ArrayList<String>(path));
return;
}
if (index == end.length()) {
return;
}
for (int pos = 0; pos < end.length(); pos++) {
for (int i = 0; i < 26; i++) {
char replace = (char)('a' + i);
String new_str = cur_str.substring(0, pos) + replace + cur_str.substring(pos+1, cur_str.length());
if (dict.contains(new_str)) {
path.add(new_str);
findPath(new_str, end, index+1, dict, path, ret);
path.remove(path.size()-1);
}
}
}
}
}
Solution 1:
Unlike Word Ladder problem, which only care about the shortest path's length. For this problem, we need to print out all shortest pathes, which is a subset of all pathes. If we use DFS, we need to traverse each of those path. Apparently, the search cost is quite expensive.
Usable version 1:
public class Solution {
public List<List<String>> findLadders(String start, String end, Set<String> dict) {
List<List<String>> ret = new ArrayList<List<String>> ();
if (start == null || end == null || dict == null)
throw new IllegalArgumentException("The passed in arguments is illegal");
ArrayList<String> path = new ArrayList<String> ();
HashSet<String> visited = new HashSet<String> ();
visited.add(start);
path.add(start);
findPath(start, end, dict, path, visited, ret);
return ret;
} private void findPath(String cur_str, String end, Set<String> dict, ArrayList<String> path, Set<String> visited, List<List<String>> ret) {
if (cur_str.equals(end)) {
ret.add(new ArrayList<String>(path));
return;
}
for (int pos = 0; pos < end.length(); pos++) {
for (int i = 0; i < 26; i++) {
char replace = (char)('a' + i);
String new_str = cur_str.substring(0, pos) + replace + cur_str.substring(pos+1, cur_str.length());
if (dict.contains(new_str) && !visited.contains(new_str)) {
visited.add(new_str);
path.add(new_str);
findPath(new_str, end, dict, path, visited, ret);
path.remove(path.size()-1);
visited.remove(new_str);
}
}
}
}
} The above code structure is easy, but the time complexity is too high, since we need to search all possible routines.
Improvement Analysis:
Apparently, we want to use the powerful BFS for this problem.
The advantage we can take: the shortest path must be first reached than other valid path. int min_level = 0;
while (!queue.isEmpty()) {
WordNode cur = queue.poll();
if (min_level != 0 && level > min_level)
continue;
...
if (min_level == 0)
min_level = level;
if (level == min_level && min_level != 0) {
...
}
} Challenge 1:
Since this problem ask us to print out all shortest pathes, it is far more hard than the previous question, since we could not easily tag all encountered words. However, For this problem, if we blindly tag all encounter words, we could lose answer.
-------------------------------------------------
Case: If we tag all words as visited we have encountered before enqueue.
start: hot
end: dog
dict: [hot, dot, hog, dog] Expected: [[hot, dot, dog], [hot, hog, dog]]
Output: [[hot, dot, dog]]
-------------------------------------------------
Step 1: equeue "hot" (tag "hot" as visited)
Step 2: dequeue "hot", enqueue "dot", "hog" (tag "dot" and "hog" as visited).
Step 3: dequeue "dot", enqueue "dog" (tag "dog" as visited).
step 4: dequeue "hog", try to enqueue "dog". (failed, because "dog" has already been tagged as visited). A way to fix this failure is never tage the "end" word as visisted.
if (!new_word.equals(end))
visited.add(new_word); But it's a little ugly, don't you think so? Challenge 2:
In BFS search, even we reached the target word, how could we recover the path that reach it.
This question has bothered me a lot, until I have seen the genius method: design the WordNode structure for recording previous node reference.
class WordNode {
String word;
WordNode pre;
public WordNode(String word, WordNode pre) {
this.word = word;
this.pre = pre;
}
} We use the queue to contain WordNode.
Queue<WordNode> queue = new LinkedList<WordNode> ();
Once we enqueue a word into the String, we wrap it with WordNode.
if (!visited.contains(new_word) && dict.contains(new_word)) {
queue.offer(new WordNode(new_word, cur)); Mis understanding: Since Java automatically collect the garbage, if we dequeue a WordNode, the information for WordNode is forever disappeared, how could we recover it? Actually, we still have a chain to point all WordNode to guarantee them not disappear. Note the arugaments we have passed to construct WordNode: new WordNode(new_word, cur). The "cur" is the reference for the current node. When we reach the target node we can trace this information back.
Note: In different search path, even for the same word, they are in different WordNode.
----------------------------------------------------------------------
if (level == min_level && min_level != 0) {
ArrayList<String> item = new ArrayList<String> ();
item.add(cur_word);
while (cur.pre != null) {
cur = cur.pre;
item.add(0, cur.word);
}
ret.add(item);
}
---------------------------------------------------------------------- Even we have solved the above two challenges, we still could make a lot mistakes for using this solving strategy.
Since we rely on "cur_num, next_num, level" to maintian the level information we need. And now we have return in the middle(before reach checking "cur_num == 0"). Any error is unavoidable in this routine.
Solution 2:
class WordNode {
String word;
WordNode pre;
public WordNode(String word, WordNode pre) {
this.word = word;
this.pre = pre;
}
}
public class Solution {
public List<List<String>> findLadders(String start, String end, Set<String> dict) {
List<List<String>> ret = new ArrayList<List<String>> ();
if (start == null || end == null || dict == null)
throw new IllegalArgumentException("The passed in arguments is illegal");
Queue<WordNode> queue = new LinkedList<WordNode> ();
HashSet<String> visited = new HashSet<String> ();
queue.offer(new WordNode(start, null));
dict.add(end);
visited.add(start);
int cur_num = 1;
int next_num = 0;
int level = 1;
int min_level = 0;
while (!queue.isEmpty()) {
WordNode cur = queue.poll();
if (min_level != 0 && level > min_level)
continue;
String cur_word = cur.word;
cur_num--;
if (end.equals(cur_word)) {
if (min_level == 0)
min_level = level;
//the first min_level was set is the lowest level
if (level == min_level && min_level != 0) {
ArrayList<String> item = new ArrayList<String> ();
item.add(cur_word);
while (cur.pre != null) {
cur = cur.pre;
item.add(0, cur.word);
}
ret.add(item);
}
if (cur_num == 0) {
cur_num = next_num;
next_num = 0;
level++;
}
continue;
}
//don't put the "end" into visited array.
char[] char_array = cur_word.toCharArray();
for (int i = 0; i < end.length(); i++) {
char copy = char_array[i];
for (char c = 'a'; c <= 'z'; c++) {
char_array[i] = c;
String new_word = new String(char_array);
if (!visited.contains(new_word) && dict.contains(new_word)) {
queue.offer(new WordNode(new_word, cur));
next_num++;
if (!new_word.equals(end))
visited.add(new_word);
}
}
char_array[i] = copy;
}
if (cur_num == 0) {
cur_num = next_num;
next_num = 0;
level++;
}
}
return ret;
}
}
Since we have already solved two important challenges in BFS.
1. how to visit the "target" word twice, thus we could record other shortest search pathes.
2. how to trace back a search path. Improvement 1:
However, the code structure we have used in "level-traverse" is still not clear enough for tackling this problem, there are too many "counts" needed to maintian properly, which makes the code hard to read and implement. A way to solve this problem is to leverge our helepr data structure "WordNode", we use it not to record the pre node of the current node, we also record the level of the current node. Since we wrap the level information with the node, the code could be quite clear and easy.
1. How to set the initial node?
queue.offer(new WordNode(null, start, 1));
The initial node is the "start", its level is 1. 2. How to specify the level for nodes at next level?
if (un_visited.contains(new_word)) {
visited.add(new_word);
queue.offer(new WordNode(cur_node, new_word, cur_node.level+1));
} 3. How to decide whether we reach a new level?
if (cur_node.level > cur_level) {
un_visited.removeAll(visited);
cur_level = cur_node.level;
} Haha...Quite smart and elegant! Don't you think so!!! Improvement 2:
Rather than avoid tagging the "end" word (which still problemetic), we can cover all shortest pathes by using two HashSets.
One hashset is called "un_visited", another hashset is called "visited". Only when we reach level 'i+1', we tag all nodes at "i" as unreachable.(remove them from un_visted hashset). Note: the visited at here means a word was visited at current level. The reason.
start: hot
end: dog
dict: [hot, dot, hog, dog] step 1: enqueue "hot", mark it as visited.
visited: ["hot"]
unvisited: ["hot", "dot", "hog", "dog"]
step 2: dequeue "hot", enqueue "dot","hog". delete "hot" from unvisited. add "hot", "dog" into visited.
visited: ["hot", "dot", "hog"]
unvisited: ["dot", "hog", "dog"]
step 3: deque "dot", enqueue "dog". "dog" into visited.
visited: ["hot", "dot", "hog", "dog"]
unvisited: ["dot", "hog", "dog"] //note: since there is still "hog" at the same level, unvisited was unchanged.<important>
step 4: deque "dot", enqueue "dog"<different WordNode>. "dog" into visited.
visited: ["hot", "dot", "hog", "dog"]
unvisited: ["dot", "hog", "dog"]
step 5: deque "dog", "dot", "hog", "dog" was removed from unvisited. <two dogs have already been enqueued at step 3 and step 4>
visited: ["hot", "dot", "hog", "dog"]
unvisited: [] So nice!!! We tag the nodes as visited level by level!!! Incase "i" level has multi nodes point to the same element at "i+1" level. Implementation:
1. prepare initial state.
Set<String> visited = new HashSet<String> ();
Set<String> un_visited = new HashSet<String> ();
int cur_level = 1;
dict.add(end);
un_visited.addAll(dict);
queue.offer(new WordNode(null, start, 1));
visited.add(start); 2. remove above(i) level visited nodes.
if (cur_node.level > cur_level) {
un_visited.removeAll(visited);
cur_level = cur_node.level;
} 3. add new words into visited array.
if (un_visited.contains(new_word)) {
visited.add(new_word);
queue.offer(new WordNode(cur_node, new_word, cur_node.level+1));
}
Solution:
class WordNode {
WordNode pre;
String word;
int level;
public WordNode(WordNode pre, String word, int level) {
this.pre = pre;
this.word = word;
this.level = level;
}
}
public class Solution {
public List<List<String>> findLadders(String start, String end, Set<String> dict) {
if (start == null || end == null || dict == null)
throw new IllegalArgumentException("The passed in arguments are illegal");
List<List<String>> ret = new ArrayList<List<String>> ();
Queue<WordNode> queue = new LinkedList<WordNode> ();
Set<String> visited = new HashSet<String> ();
Set<String> un_visited = new HashSet<String> ();
int cur_level = 1;
dict.add(end);
un_visited.addAll(dict);
queue.offer(new WordNode(null, start, 1));
visited.add(start);
while (!queue.isEmpty()) {
WordNode cur_node = queue.poll();
if (cur_node.level > cur_level) {
un_visited.removeAll(visited);
cur_level = cur_node.level;
}
String cur_word = cur_node.word;
if (cur_word.equals(end)) {
ArrayList<String> item = new ArrayList<String> ();
while (cur_node != null) {
item.add(0, cur_node.word);
cur_node = cur_node.pre;
}
ret.add(item);
continue;
}
char[] char_array = cur_word.toCharArray();
for (int i = 0; i < end.length(); i++) {
char temp = char_array[i];
for (char c = 'a'; c <= 'z'; c++) {
char_array[i] = c;
String new_word = new String(char_array);
if (un_visited.contains(new_word)) {
visited.add(new_word);
queue.offer(new WordNode(cur_node, new_word, cur_node.level+1));
}
}
char_array[i] = temp;
}
}
return ret;
}
}
[LeetCode#128]Word Ladder II的更多相关文章
- [Leetcode Week5]Word Ladder II
Word Ladder II 题解 原创文章,拒绝转载 题目来源:https://leetcode.com/problems/word-ladder-ii/description/ Descripti ...
- 【leetcode】Word Ladder II
Word Ladder II Given two words (start and end), and a dictionary, find all shortest transformation ...
- Java for LeetCode 126 Word Ladder II 【HARD】
Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from ...
- [LeetCode] 126. Word Ladder II 词语阶梯 II
Given two words (beginWord and endWord), and a dictionary's word list, find all shortest transformat ...
- LeetCode 126. Word Ladder II 单词接龙 II(C++/Java)
题目: Given two words (beginWord and endWord), and a dictionary's word list, find all shortest transfo ...
- [LeetCode] 126. Word Ladder II 词语阶梯之二
Given two words (beginWord and endWord), and a dictionary's word list, find all shortest transformat ...
- [Leetcode][JAVA] Word Ladder II
Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from ...
- leetcode 126. Word Ladder II ----- java
Given two words (beginWord and endWord), and a dictionary's word list, find all shortest transformat ...
- Leetcode#126 Word Ladder II
原题地址 既然是求最短路径,可以考虑动归或广搜.这道题对字典直接进行动归是不现实的,因为字典里的单词非常多.只能选择广搜了. 思路也非常直观,从start或end开始,不断加入所有可到达的单词,直到最 ...
随机推荐
- tp集成支付宝担保支付
现在的网站功能越来越全乎了,很多网站都需要做支付功能,而且很多大平台都提供了各式各样的api来扩充自己的用户和开发者.话说,这种使用大平台的api来做支付,无论是从成本上还是从开发效率上都是很好的选择 ...
- 在Abp框架中使用Mysql数据库的方法以及相关问题小记
最近发现了一款DDD的框架 看起来不错,据说挺流弊的 刚好最近要弄点小东西,拿来试试也不错 苦于穷逼买不起高配服务器,只好装mysql数据库了 下面说下如何在该框架下使用Mysql数据库 打开项目后, ...
- PHP语言、浏览器、操作系统、IP、地理位置、ISP
)]; } else { $Isp = 'None'; } return $Isp; }}
- iOS 十六进制的相加取反
ios中将NSstring字符串转换成char类型 NSString *string = [NSString stringWithFormat:@"5D"]; const char ...
- CSS Margin(外边距)
CSS Margin(外边距)属性定义元素周围的空间. Margin margin清除周围的元素(外边框)的区域.margin没有背景颜色,是完全透明的 margin可以单独改变元素的上,下,左,右边 ...
- jQuery 选择器【1】
jQuery 选择器 请使用我们的 jQuery 选择器检测器 来演示不同的选择器. 选择器 实例 选取 * $("*") 所有元素 #id $("#lastname&q ...
- (三)跟我一起玩Linux网络服务:DHCP服务配置之主服务器配置
我们今天来做DHCP服务器的配置,我们的前提示要实现用一台虚拟机做DHCP服务器 1.首先,我们要有DHCP软件,我们用到下面两个软件(可以使用其他方法从网上直接安装,具体方法网络搜索) dhcp-3 ...
- 无线端web开发学习总结
无线web开发之前要做一些准备工作:一.必需的reset样式库1.其中的重点是盒模型box-sizing:由原来pc端的content-box改为border-box. *, *:before, *: ...
- JavaScript 实现触点式弹出菜单插件
之前做项目时经常用到一种触点式弹出菜单或者导航的功能,这个功能的主要应用场景是:web页面中多层分级导航或者子功能目录,但又考虑到页面区域有限,于是就考虑到在鼠标移动到某导航按钮上或者点击时,系统将在 ...
- 关于webapp的一个webframe问题
最近重启ios webapp的项目,将之前的框架拿过来发现出现了错误,错误出现在写JSAlart控件的WebFrame上,xcode会报WebFrame是未定义的错误.由于之前使用的是ios5的 sd ...