【Lintcode】121. Word Ladder II

题目地址:

https://www.lintcode.com/problem/word-ladder-ii/description
给定两个字符串 s s s e e e,再给定一个含字符串的哈希表,从 s s s开始出发,每次允许改变其中的一个字母,问多少至少步能走到 e e e,并要求按字典序返回所有最短路径,并且要求路径中所有字符串都包含于哈希表内(起点终点不必)。

关于BFS找路径的标准做法,参考https://blog.csdn.net/qq_46105170/article/details/108415255。下面介绍另一种做法。

思路是BFS建图 + DFS。如果起点终点一样,情形是平凡的。题目本质是隐式图搜索,而且要求最短路径,所以要用BFS分层遍历,并且要维护层与层之间的路径(要注意排除掉层内部单词的路径,这些路径是无效的)。我们可以采用逆向遍历的方式,从 e e e一路BFS到 s s s,建一个从 s s s e e e的一个拓扑图,接下来只需要做一遍DFS就可以了(其实BFS的方向无所谓,都可以做,只要最后建出来的图DFS方便即可)。由于要求路径按字典序排列,所以在建图的时候可以用邻接表,并且value是一个TreeSet,这样一个顶点的邻接点会按照字典序排序,DFS的时候会将路径按字典序由小到大遍历出来,最后就不用再排序了。代码如下:

import java.util.*;

public class Solution {
    /*
     * @param start: a string
     * @param end: a string
     * @param dict: a set of string
     * @return: a list of lists of string
     */
    public List<List<String>> findLadders(String start, String end, Set<String> dict) {
        // write your code here
        List<List<String>> res = new ArrayList<>();
        // 特判
        if (start.equals(end)) {
            List<String> list = new ArrayList<>();
            list.add(start);
            res.add(list);
            return res;
        }
    	
    	// 为了方便,把起点和终点都加入dict
        dict.add(start);
        dict.add(end);
    
    	// 开一个哈希表用作邻接表建图
        Map<String, Set<String>> graph = new HashMap<>();
        // 为了BFS建一个队列和哈希表,并将end加入;从end开始做BFS
        Queue<String> queue = new LinkedList<>();
        queue.offer(end);
        Set<String> visited = new HashSet<>();
        visited.add(end);
        
		boolean found = false;
        while (!queue.isEmpty()) {
        	// 先把queue里这一层的节点全标记为visited,这样在扩展下一层的时候可以避免本层顶点被纳入进来
            visited.addAll(queue);
            
            // 存下一层要入队的字符串,由于下一层字符串可能有重复,这里用个哈希表去重
            Set<String> nextLayer = new HashSet<>();
            // 分层BFS需要记录队列的size
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                String cur = queue.poll();
                // 返回cur的不在本层的邻接顶点
                for (String next : getNexts(cur, dict, visited)) {
                	// 开始从next向cur连一条边
                    graph.putIfAbsent(next, new TreeSet<>());
                    graph.get(next).add(cur);
                    // 如果到达了start,就标记一下
                    if (next.equals(start)) {
                        found = true;
                    }
                    
                    nextLayer.add(next);       
                }
            }
			
			// 建图结束,不要再向后建了,否则DFS的时候会产生一个更长的路径
			if (found) {
                break;
            }
			
 			// 把下一层的节点加入队列
            queue.addAll(nextLayer);
        }
        
        dfs(start, end, graph, new ArrayList<>(), res);
        return res;
    }
    
    // 从cur开始一路DFS到end,并将所有路径加入res
    // 这里DFS的时候并不需要记录visited,原因是图本身已经是分好层的,也就是没有环,类似于树的形状(只是类似,但并不是)
    private void dfs(String cur, String end, Map<String, Set<String>> graph, List<String> list, List<List<String>> res) {
        list.add(cur);
        if (cur.equals(end)) {
            res.add(new ArrayList<>(list));
            // 回溯的时候恢复现场
            list.remove(list.size() - 1);
            return;
        }
    
        if (graph.containsKey(cur)) {
            for (String next : graph.get(cur)) {
                dfs(next, end, graph, list, res);
            }
        }
        
        // 回溯的时候恢复现场
        list.remove(list.size() - 1);
    }
    
    private Set<String> getNexts(String cur, Set<String> dict, Set<String> visited) {
        Set<String> nexts = new HashSet<>();
        char[] chs = cur.toCharArray();
        for (int i = 0; i < chs.length; i++) {
            char ch = chs[i];
            for (char c = 'a'; c <= 'z'; c++) {
                chs[i] = c;
                String next = String.valueOf(chs);
                if (dict.contains(next) && !visited.contains(next)) {
                    nexts.add(next);
                }
            }
            chs[i] = ch;
        }
        
        return nexts;
    }
}

时空复杂度 O ( V log ⁡ V + E ) O(V\log V+E) O(VlogV+E) log ⁡ V \log V logV的原因是用了TreeSet而不是普通的HashSet。

注解:
1、关键的一步是,在BFS的时候,一开始就要将queue里记录的当前层的所有顶点都加入哈希表里,这样getNexts的时候就不会得到当前层的顶点,而只会得到下一层的顶点了。可以说visited.addAll(queue);这一句所处的位置相当的关键。如果和普通的BFS一样,将这一句放在遍历next的循环里,就会漏掉一部分路径,也就是会漏解;
2、nextLayer这个变量是用来存储下一层要入队的节点的,这样可以防止同一个字符串重复入队,可以提高效率;
3、found变量主要用来记录是否到达了start,一旦到达就说明找到了最短路了,这时候建完从start到本层的cur的边后就不能继续建图了,否则会产生一条更长的路径,DFS的时候就会产生错误的结果(当然也可以记录步数,在DFS的时候排除掉更长的路径)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值