《程序员代码面试指南》单词最短转换路径问题——java实现

单词最短转换路径问题

题目描述:

给定两个单词beginWordendWord,还有一本词典是list类型。
找到所有从beginWord变到endWord的最短转换路径,变动的规则是:
1,一次只能变一个位置的字符
2,每一个转换后的word一定要在list中
3,初始时list中没有beginWord这个词
示例:

输入: beginWord = “hit”
endWord = “cog”
wordList = [“hot”,”dot”,”dog”,”lot”,”log”,”cog”]
返回: [ [“hit”,”hot”,”dot”,”dog”,”cog”], [“hit”,”hot”,”lot”,”log”,”cog”] ]

注意:
1,返回值的类型为List(List(String))
2,如果不存在转化路径请返回空链表(不是null)
3,所有的词一定都是相同长度的
4,所有词都是小写的a~z
5,在list中没有重复的词
6,beginWord和endWord都不是空字符串或者null

题目难度:

hard

题目思路:

1、先获取每个单词的临近节点,也就是每个单词换一个字母后是否在list词典中。具体方法:首先把beginWord字符串添加到字典中。遍历到当前节点时,则改变该单词每一个位置上的元素。然后判断新的单词是否在词典中出现,以及是否在set集合中。然后存入hashmap中,key就为这个单词,value就为这个单词的临近节点(list型)。这样遍历结束后,词典中每个单词的邻居结点都找到了。
2、记录每个单词到第一个单词的距离。此处记录的距离类似树的宽度优先遍历。距离每个单词到顶层的距离。
3、最后求最短转换路径并输出最短路径。知道每个单词的临近单词,以及每个单词到头结点的距离,就可以求所有走过的最短路径并输出。

代码实现:

import java.util.*;

/**
 * Created by Zhaoyang Ge on 2018/9/7.
 */

/**
 * 本题要求头单词到尾单词在字典中的最短路径
 * 1、步骤一:要求每个单词的邻居结点,存到map中,key为改单词,value为list链表,该单词的所有邻居。
 * 2、步骤二:要求出每个单词到头单词的距离。
 * 3、步骤三:要求出头单词对应的每个最短转换路径,且最短转换路径可能不唯一。
 */
public class WordShortestConversionPath {
    public static List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
        wordList.add(beginWord);
        HashMap<String, ArrayList<String>> nexts = getNexts(wordList);       //得到所有单词的邻居结点存进map中
        HashMap<String, Integer> distances = getDistances(beginWord, nexts); //得到每个单词到头结点的距离
        LinkedList<String> pathList = new LinkedList<>();
        List<List<String>> res = new ArrayList<>();
        getShortestPath(beginWord, endWord, nexts, distances, pathList, res);  //得到最短路径并输出最短路径
        return res;

    }

    private static HashMap<String, ArrayList<String>> getNexts(List<String> words) {
        HashSet<String> dict = new HashSet<>(words);    //把每个单词放到set集合中去重,并得到每个单词的邻居结点
        HashMap<String, ArrayList<String>> nexts = new HashMap<>();

        for (int i = 0; i < words.size(); i++) {
            nexts.put(words.get(i), getNext(words.get(i), dict));      //map中放着,每个单词及该单词的邻居结点
        }
        return nexts;
    }

    //求单个单词对应的邻居结点。之所以采用a-z的每个位置组合新单词,然后查看词典是否存在,这样时间复杂度为o(26N)
    //而采用查到当前单词,然后遍历一下字典看看是否存在,这种时间复杂度为o(n^2),当词典很大,相对来说常数级别可以忽略
    private static ArrayList<String> getNext(String word, HashSet<String> dict) {
        ArrayList<String> res = new ArrayList<>();
        char[] chs = word.toCharArray();
        for (char cur = 'a'; cur <= 'z'; cur++) {//a-z依次与每个位置的值进行替换,组合成新的单词,并查看该单词在字典中是否存在
            for (int i = 0; i < chs.length; i++) {
                if (chs[i] != cur) {
                    char tmp = chs[i];
                    chs[i] = cur;
                    if (dict.contains(String.valueOf(chs))) {
                        res.add(String.valueOf(chs)); //该步骤会将word在词典中的所以邻居结点都添加到res链表中
                    }
                    chs[i] = tmp;
                }

            }
        }
        return res;   //返回该单词对应的所有邻居结点
    }

    //要求每个单词到头结点的距离问题,其实就是宽度优先遍历问题,遍历到每一行,则距离+1
    private static HashMap<String, Integer> getDistances(String beginWord, HashMap<String, ArrayList<String>> nexts) {
        HashMap<String, Integer> distances = new HashMap<>();
        distances.put(beginWord, 0);
        Queue<String> queue = new LinkedList<>();
        queue.add(beginWord);
        HashSet<String> set = new HashSet<>();//若,上次某个节点查询完了,则接下来的邻居结点就不需要再查,如 a-b是邻居
        // 则b-a就不需要算距离了
        set.add(beginWord);
        while (queue != null) {
            String cur = queue.poll();
            for (String str : nexts.get(cur)) {  //当前节点的所有邻居结点
                if (!set.contains(str)) {
                    set.add(str);
                    queue.add(str);
                    distances.put(str, distances.get(cur) + 1);
                }
            }
        }
        return distances;
    }

    //其实得到最短路径并输出该路径的问题,就是深度优先遍历的问题
    private static void getShortestPath(String cur, String end, HashMap<String, ArrayList<String>> nexts,
                                        HashMap<String, Integer> distances, LinkedList<String> solution,
                                        List<List<String>> res) {
        solution.add(cur);
        if (end.equals(cur)) {
            res.add(new LinkedList<>(solution));  //为每一次输出的最短转换路径添加到新的链表中
        } else {
            for (String next : nexts.get(cur)) {
                if (distances.get(next) == distances.get(cur) + 1) {//只有满足下一个邻居结点是从上一个结点来的,才可能是最短距离
                    getShortestPath(next, end, nexts, distances, solution, res);
                }
            }
        }
        solution.pollLast(); //最后要把最后加进来的节点依次删除,是为了寻找其他的转换路径
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值