## LeetCode Top Interview Questions 127. Word Ladder (Java版; Medium)

#### 题目描述

`Given two words (beginWord and endWord), and a dictionary's word list, find the length of shortesttransformation sequence from beginWord to endWord, such that:Only one letter can be changed at a time.Each transformed word must exist in the word list. Note that beginWord is not a transformed word.Note:Return 0 if there is no such transformation sequence.All words have the same length.All words contain only lowercase alphabetic characters.You may assume no duplicates in the word list.You may assume beginWord and endWord are non-empty and are not the same.Example 1:Input:beginWord = "hit",endWord = "cog",wordList = ["hot","dot","dog","lot","log","cog"]Output: 5Explanation: As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",return its length 5.Example 2:Input:beginWord = "hit"endWord = "cog"wordList = ["hot","dot","dog","lot","log"]Output: 0Explanation: The endWord "cog" is not in wordList, therefore no possible transformation.`

#### 第一次做; 融合了力扣题解的通用状态哈希表以及LeetCode题解的双向bfs; 核心: 双向bfs并不是同时双向, 实际上是这次从beginWord方向bfs,下次从endWord方向bfs; 代码中实现双向遍历的方式是while循环中的第一个if语句; 代码里没法实现真正的同时双向bfs, 我想了想, 同不同时并不重要, 重要的是从两端进行bfs; 我的感觉是, 仅从beginWord端进行bfs时, 队列中的元素越来越多, 直到队列中出现endWord才停下, 此时队列中包含了大量无关的元素, 如果从两个方向进行bfs, 队列中的无关元素会少一些

`/*双向bfs并不是从两端同时bfs, 实际上是这次从begin进行bfs, 下次从end进行bfs, 像这样循环操作*/class Solution {    public int ladderLength(String beginWord, String endWord, List<String> wordList) {        //endWord也是transformed word, 所以必须存在于wordList中, 否则返回0, 表示无法从beginWord变成endWord        if(!wordList.contains(endWord))            return 0;        //        int n = beginWord.length();        //key是通用状态; value是拥有该通用状态的词        HashMap<String,ArrayList<String>>all_commons = new HashMap<>();        //记录wordList中所有元素对应的所有通用状态        wordList.forEach(                word->{                    for(int i=0; i<n; i++){                        String common = word.substring(0,i)+"*"+word.substring(i+1);                        if(!all_commons.containsKey(common))                            all_commons.put(common, new ArrayList<String>());                        all_commons.get(common).add(word);                    }                }        );        //        //使用HashSet实现宽度优先遍历bfs        HashSet<String> begin = new HashSet<>();        HashSet<String> end = new HashSet<>();        begin.add(beginWord);        end.add(endWord);        //记录访问过的节点        HashSet<String> visited = new HashSet<>();        //细节: 返回值的初始化, 由于beginWord!=endWord, 所以至少需要一步变化        int len = 1;        while(!begin.isEmpty() && !end.isEmpty()){            //核心:控制当前循环从哪个方向进行bfs; 让begin指向size更小的集合, 这样就不会一直从一个方向bfs了            if(begin.size()>end.size()){                HashSet<String> tmp = begin;                begin = end;                end = tmp;            }            //记录遍历begin时每个元素的邻居, 也就是cur的邻居            HashSet<String> neighbor = new HashSet<>();            for(String cur : begin){                //遍历cur的邻居                for(int i=0; i<n; i++){                    String tmp = cur.substring(0,i)+"*"+cur.substring(i+1);                    //有了all_commons哈希表,就不用每个位置都遍历'a'~'z'了                    //细节:如果cur是beginWord的话, all_commons没有统计beginWord的通用状态, 所以all_commons.get(tmp)可能返回null, 所以要提前检查一下                    if(!all_commons.containsKey(tmp))                        continue;                    for(String str : all_commons.get(tmp)){                        if(end.contains(str))                            return len+1;                        if(!visited.contains(str)){                            visited.add(str);                            //记录cur的邻居                            neighbor.add(str);                        }                    }                }            }            //处理完begin中的元素后, 让begin指向begin中的元素的邻居            begin = neighbor;            //路径长度++            len++;        }        //执行到这里说明双向bfs没有相遇, 也就是没有找到从beginWord到endWord的路径        return 0;    }}`

#### 第一次做; 模仿了LeetCode上的双向bfs; 核心:使用HashSet记录邻居, 遍历完set中的元素后, 让set指向新邻居, 从而实现bfs

`/*双向bfs并不是从两端同时bfs, 实际上是这次从begin进行bfs, 下次从end进行bfs, 像这样循环操作*/class Solution {    public int ladderLength(String beginWord, String endWord, List<String> wordList) {        //endWord也是transformed word, 所以必须存在于wordList中, 否则返回0, 表示无法从beginWord变成endWord        if(!wordList.contains(endWord))            return 0;        //        int n = beginWord.length();        //使用HashSet实现宽度优先遍历bfs        HashSet<String> begin = new HashSet<>();        HashSet<String> end = new HashSet<>();        begin.add(beginWord);        end.add(endWord);        //记录访问过的节点        HashSet<String> visited = new HashSet<>();        //细节: 返回值的初始化, 由于beginWord!=endWord, 所以至少需要一步变化        int len = 1;        while(!begin.isEmpty() && !end.isEmpty()){            //核心:控制当前循环从哪个方向进行bfs; 让begin指向size更小的集合, 这样就不会一直从一个方向bfs了            if(begin.size()>end.size()){                HashSet<String> tmp = begin;                begin = end;                end = tmp;            }            //记录遍历begin时每个元素的邻居, 也就是cur的邻居            HashSet<String> neighbor = new HashSet<>();            for(String cur : begin){                char[] chs = cur.toCharArray();                //每个位置都遍历一遍'a'~'z'                for(int i=0; i<n; i++){                    char old = chs[i];                    //直接使用char作为循环条件                    for(char ch='a'; ch<='z'; ch++){                        chs[i] = ch;                        //maybe可能存在于worList中; 也可能不在worList中                        String maybe = String.valueOf(chs);                        //如果双向bfs相遇, 则说明找到了beginWord到endWord的转换路径, 返回路径长度                        if(end.contains(maybe))                            return len+1;                        //如果maybe在wordList中,并且没有访问过maybe                        if(wordList.contains(maybe) && !visited.contains(maybe)){                            visited.add(maybe);                            //maybe加入到cur的邻居                            neighbor.add(maybe);                        }                    }                    //处理完cur的第i个元素后, 撤销修改                    chs[i] = old;                }            }            //处理完begin中的元素后, 让begin指向begin中各个元素的邻居            begin = neighbor;            //路径长度++            len++;        }        //执行到这里说明双向bfs没有相遇, 也就是没有找到从beginWord到endWord的路径        return 0;       }}`

#### 第一次做; 转成图的问题, 使用宽度优先遍历; 并不是动态规划; jdk1.8的匿名函数写法; javafx需要自行安装; 核心: 通用状态; 什么是相邻? 拥有相同通用状态的两个String互为邻居; 预处理操作在广度优先搜索之前高效的建立了邻接表

`class Solution {    public int ladderLength(String beginWord, String endWord, List<String> wordList) {        //每个单词的长度        int n = beginWord.length();        //通用状态哈希表; key是某个通用状态, value是拥有该通用状态的词的集合        HashMap<String, ArrayList<String>> all_commons = new HashMap<>();        //统计字典中所有词的通用状态; jdk1.8中的匿名函数        wordList.forEach(                word -> {                    for(int i=0; i<n; i++){                        String common = word.substring(0,i)+"*"+word.substring(i+1);                        ArrayList<String> cur = all_commons.getOrDefault(common, new ArrayList<String>());                        cur.add(word);                        all_commons.put(common, cur);                    }                }        );        //记录bfs过程中已经访问过的节点        HashSet<String> set = new HashSet<>();        //队列; 用于bfs        LinkedList<Record> queue = new LinkedList<>();        queue.add(new Record(beginWord, 1));        set.add(beginWord);        //bfs        while(!queue.isEmpty()){            Record r = queue.poll();            String cur = r.word;            set.add(cur);            //遍历通用状态            for(int i=0; i<n; i++){                String tmp = cur.substring(0,i)+"*"+cur.substring(i+1);                //如果没有当前这个通用状态就进入下一轮循环                if(!all_commons.containsKey(tmp))                    continue;                //邻居节点入队; 什么是邻居? 拥有相同通用状态的两个String互为邻居                for(String str : all_commons.get(tmp)){                    if(str.equals(endWord))                        return r.level+1;                    if(set.contains(str))                        continue;                    set.add(str);                    queue.add(new Record(str, r.level+1));                }            }        }        return 0;    }    public class Record{        String word;        Integer level;        Record(String word, Integer level){            this.word = word;            this.level = level;        }    }}`

#### ​​LeetCode题解​​; 使用HashSet这个结构也能实现bfs, 不一定非得用队列

`public class Solution {public int ladderLength(String beginWord, String endWord, Set<String> wordList) {  Set<String> beginSet = new HashSet<String>(), endSet = new HashSet<String>();  int len = 1;  int strLen = beginWord.length();  HashSet<String> visited = new HashSet<String>();    beginSet.add(beginWord);  endSet.add(endWord);  while (!beginSet.isEmpty() && !endSet.isEmpty()) {    if (beginSet.size() > endSet.size()) {      Set<String> set = beginSet;      beginSet = endSet;      endSet = set;    }    Set<String> temp = new HashSet<String>();    for (String word : beginSet) {      char[] chs = word.toCharArray();      for (int i = 0; i < chs.length; i++) {        for (char c = 'a'; c <= 'z'; c++) {          char old = chs[i];          chs[i] = c;          String target = String.valueOf(chs);          if (endSet.contains(target)) {            return len + 1;          }          if (!visited.contains(target) && wordList.contains(target)) {            temp.add(target);            visited.add(target);          }          chs[i] = old;        }      }    }    beginSet = temp;    len++;  }    return 0;}}`

#### ​​力扣题解​​

`import javafx.util.Pair;class Solution {  public int ladderLength(String beginWord, String endWord, List<String> wordList) {    // Since all words are of same length.    int L = beginWord.length();    // Dictionary to hold combination of words that can be formed,    // from any given word. By changing one letter at a time.    HashMap<String, ArrayList<String>> allComboDict = new HashMap<String, ArrayList<String>>();    wordList.forEach(        word -> {          for (int i = 0; i < L; i++) {            // Key is the generic word            // Value is a list of words which have the same intermediate generic word.            String newWord = word.substring(0, i) + '*' + word.substring(i + 1, L);            ArrayList<String> transformations =                allComboDict.getOrDefault(newWord, new ArrayList<String>());            transformations.add(word);            allComboDict.put(newWord, transformations);          }        });    // Queue for BFS    Queue<Pair<String, Integer>> Q = new LinkedList<Pair<String, Integer>>();    Q.add(new Pair(beginWord, 1));    // Visited to make sure we don't repeat processing same word.    HashMap<String, Boolean> visited = new HashMap<String, Boolean>();    visited.put(beginWord, true);    while (!Q.isEmpty()) {      Pair<String, Integer> node = Q.remove();      String word = node.getKey();      int level = node.getValue();      for (int i = 0; i < L; i++) {        // Intermediate words for current word        String newWord = word.substring(0, i) + '*' + word.substring(i + 1, L);        // Next states are all the words which share the same intermediate state.        for (String adjacentWord : allComboDict.getOrDefault(newWord, new ArrayList<String>())) {          // If at any point if we find what we are looking for          // i.e. the end word - we can return with the answer.          if (adjacentWord.equals(endWord)) {            return level + 1;          }          // Otherwise, add it to the BFS Queue. Also mark it visited          if (!visited.containsKey(adjacentWord)) {            visited.put(adjacentWord, true);            Q.add(new Pair(adjacentWord, level + 1));          }        }      }    }    return 0;  }}`

`import javafx.util.Pair;class Solution {  private int L;  private HashMap<String, ArrayList<String>> allComboDict;  Solution() {    this.L = 0;    // Dictionary to hold combination of words that can be formed,    // from any given word. By changing one letter at a time.    this.allComboDict = new HashMap<String, ArrayList<String>>();  }  private int visitWordNode(      Queue<Pair<String, Integer>> Q,      HashMap<String, Integer> visited,      HashMap<String, Integer> othersVisited) {    Pair<String, Integer> node = Q.remove();    String word = node.getKey();    int level = node.getValue();    for (int i = 0; i < this.L; i++) {      // Intermediate words for current word      String newWord = word.substring(0, i) + '*' + word.substring(i + 1, L);      // Next states are all the words which share the same intermediate state.      for (String adjacentWord : this.allComboDict.getOrDefault(newWord, new ArrayList<String>())) {        // If at any point if we find what we are looking for        // i.e. the end word - we can return with the answer.        if (othersVisited.containsKey(adjacentWord)) {          return level + othersVisited.get(adjacentWord);        }        if (!visited.containsKey(adjacentWord)) {          // Save the level as the value of the dictionary, to save number of hops.          visited.put(adjacentWord, level + 1);          Q.add(new Pair(adjacentWord, level + 1));        }      }    }    return -1;  }  public int ladderLength(String beginWord, String endWord, List<String> wordList) {    if (!wordList.contains(endWord)) {      return 0;    }    // Since all words are of same length.    this.L = beginWord.length();    wordList.forEach(        word -> {          for (int i = 0; i < L; i++) {            // Key is the generic word            // Value is a list of words which have the same intermediate generic word.            String newWord = word.substring(0, i) + '*' + word.substring(i + 1, L);            ArrayList<String> transformations =                this.allComboDict.getOrDefault(newWord, new ArrayList<String>());            transformations.add(word);            this.allComboDict.put(newWord, transformations);          }        });    // Queues for birdirectional BFS    // BFS starting from beginWord    Queue<Pair<String, Integer>> Q_begin = new LinkedList<Pair<String, Integer>>();    // BFS starting from endWord    Queue<Pair<String, Integer>> Q_end = new LinkedList<Pair<String, Integer>>();    Q_begin.add(new Pair(beginWord, 1));    Q_end.add(new Pair(endWord, 1));    // Visited to make sure we don't repeat processing same word.    HashMap<String, Integer> visitedBegin = new HashMap<String, Integer>();    HashMap<String, Integer> visitedEnd = new HashMap<String, Integer>();    visitedBegin.put(beginWord, 1);    visitedEnd.put(endWord, 1);    while (!Q_begin.isEmpty() && !Q_end.isEmpty()) {      // One hop from begin word      int ans = visitWordNode(Q_begin, visitedBegin, visitedEnd);      if (ans > -1) {        return ans;      }      // One hop from end word      ans = visitWordNode(Q_end, visitedEnd, visitedBegin);      if (ans > -1) {        return ans;      }    }    return 0;  }}`