[LeetCode] Word Ladder

来源:互联网 发布:mac os怎么分区 编辑:程序博客网 时间:2024/06/06 01:56


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(因为即使一找到了一种解,也不能保证是最优解,所以必须遍历所有的可能性。),而应该使用DP或者贪心算法的思路。

可是,这题如果使用DP自底向上构造搜索树,那么会构造出太多太多的节点,显然内存开销会无法承受。所以唯一的出路就是自上而下的贪心搜索了。由于不能使用DFS,所以只能考虑BFS了。根据求最短路径的性质,BFS实际上就属于贪心算法了,因为一旦搜索成功,返回的肯定是深度最小的节点。

这题还存在一个可以利用的性质:当一个衍生词在词典中第一次搜到了以后,便可以从词典中删除了。这是由于两个原因:

1)这个衍生词很可能不会再被用到;

2)就算这个衍生词之后再被用到,那么所求出的距离肯定是绕了弯路的,(例如A->B->C->B->D,可以直接从B衍生到D而不经过C),所以所求出来的也肯定不会是最短距离了。

这里由于要求出搜索树的深度,所以我用了两个队列,一个储存当前层的节点,一个用来储存下一层的节点,每当一层遍历完后,就交换2个队列。注意,如果两个队列都为空了还没找到,需要允许额外进行最后一次搜索,因为要允许所有的词都被用为过渡的情况。一旦找到一个符合条件的,可以立刻返回,肯定是最短路径了。注意这里距离的初始值要设为1,因为即使没有用到任何词过渡,也是经过了1次的转换。

为了懒得写嵌套循环,而又允许当两个队列为空了以后还能进行最后一次搜索,这里弄了个count,值一直是当前队列的长度,最后要大于-1才跳出循环。

public int ladderLength(String start, String end,HashSet<String> dict) {int dist = 1;Queue<String> queue = new LinkedList<String>();Queue<String> nextLevelQueue = new LinkedList<String>();queue.offer(start);int count = queue.size();while (count > -1) {String curStr = queue.poll();for (int i = 0; i < curStr.length(); i++) {for (char c = 'a'; c <= 'z'; c++) {StringBuilder sb = new StringBuilder(curStr);sb.setCharAt(i, c);String nextStr = sb.toString();if (nextStr.equals(end)) {return dist + 1;}else if (dict.contains(nextStr)) {dict.remove(nextStr);nextLevelQueue.offer(nextStr);}}}count = nextLevelQueue.size();if (queue.isEmpty()) {dist++;// swap the two queuesif (!nextLevelQueue.isEmpty()) {Queue<String> temp;temp = queue;queue = nextLevelQueue;nextLevelQueue = temp;} else {count--; // count = -1}}}return 0;}


注意Java里String的replace用起来没有c++的那么强大,不支持对某一个位置上的字符进行替换,所以这里用了StringBuilder来生成衍生词。注意得尽量减少StringBuilder和String之间的切换,第一次尝试的时候就因为把队列元素类型设成了StirngBuilder,中间转换多了几次,结果就超时了。。。


网上搜了下,发现大多数解法都用了hash map,这样层次遍历起来代码更简洁,不用像我这样弄成队列切换的形式。其中key表示每个搜索节点对应的词,value表示深度。


public int ladderLength(String start, String end,HashSet<String> dict) {Queue<String> q = new LinkedList<String>();Map<String, Integer> ladderMap = new HashMap<String, Integer>();q.offer(start);ladderMap.put(start, 1);while (!q.isEmpty()) {String curStr = q.poll();int dist = ladderMap.get(curStr) + 1;for (int i = 0; i < curStr.length(); i++) {StringBuilder sb = new StringBuilder(curStr);for (char c = 'a'; c <= 'z'; c++) {sb.setCharAt(i, c);String nextStr = sb.toString();if (nextStr.equals(end))return dist;if (dict.contains(nextStr)) {ladderMap.put(nextStr, dist);q.offer(nextStr);dict.remove(nextStr);if (dict.isEmpty())break;}}}}return 0;}


如果这样做的话,就算不利用之前提到的性质,也是可以顺利AC的。因为hash map可以允许用O(1)的时间检查是否再次碰到已经访问过的节点。这样,不需要从dict里删除已经访问过的词以及判断dict是否为空的代码,只需在dict.contains(nextStr)前加上“!ladderMap.containsKey(nextStr)"即可。