[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(因为即使一找到了一种解,也不能保证是最优解,所以必须遍历所有的可能性。),而应该使用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 queues
				if (!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)"即可。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值