|
1 | 1 | package com.leetcode.graphs;
|
2 | 2 |
|
3 |
| - |
4 | 3 | import javafx.util.Pair;
|
5 |
| - |
6 | 4 | import java.util.*;
|
7 | 5 |
|
8 |
| -import static org.junit.jupiter.api.Assertions.assertEquals; |
9 |
| - |
10 | 6 | /**
|
11 | 7 | * Level: Medium
|
12 | 8 | * Link: https://leetcode.com/problems/word-ladder/
|
13 | 9 | * Description:
|
14 | 10 | * Given two words (beginWord and endWord), and a dictionary's word list, find the length of shortest transformation
|
15 | 11 | * sequence from beginWord to endWord, such that:
|
16 |
| - * <p> |
| 12 | + * |
17 | 13 | * Only one letter can be changed at a time. Each transformed word must exist in the word list. Note that beginWord
|
18 | 14 | * is not a transformed word.
|
19 |
| - * <p> |
| 15 | + * |
20 | 16 | * Note:
|
21 | 17 | * - Return 0 if there is no such transformation sequence.
|
22 | 18 | * - All words have the same length.
|
23 | 19 | * - All words contain only lowercase alphabetic characters.
|
24 | 20 | * - You may assume no duplicates in the word list.
|
25 | 21 | * - You may assume beginWord and endWord are non-empty and are not the same.
|
26 |
| - * <p> |
| 22 | + * |
27 | 23 | * Example 1:
|
28 | 24 | * Input:
|
29 | 25 | * beginWord = "hit",
|
30 | 26 | * endWord = "cog",
|
31 | 27 | * wordList = ["hot","dot","dog","lot","log","cog"]
|
32 |
| - * <p> |
| 28 | + * |
33 | 29 | * Output: 5
|
34 |
| - * <p> |
| 30 | + * |
35 | 31 | * Explanation: As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
|
36 | 32 | * return its length 5.
|
37 |
| - * <p> |
| 33 | + * |
38 | 34 | * Example 2:
|
39 | 35 | * Input:
|
40 | 36 | * beginWord = "hit"
|
41 | 37 | * endWord = "cog"
|
42 | 38 | * wordList = ["hot","dot","dog","lot","log"]
|
43 |
| - * <p> |
| 39 | + * |
44 | 40 | * Output: 0
|
45 |
| - * <p> |
46 |
| - * Explanation: The endWord "cog" is not in wordList, therefore no possible transformation. |
47 | 41 | *
|
48 |
| - * @author rampatra |
49 |
| - * @since 2019-08-15 |
| 42 | + * Explanation: The endWord "cog" is not in wordList, therefore no possible transformation. |
50 | 43 | */
|
51 | 44 | public class WordLadder {
|
52 | 45 |
|
53 |
| - /** |
54 |
| - * Runtime: <a href="https://leetcode.com/submissions/detail/251960230/">79 ms</a>. |
55 |
| - * |
56 |
| - * @param beginWord |
57 |
| - * @param endWord |
58 |
| - * @param wordList |
59 |
| - * @return |
60 |
| - */ |
| 46 | + private static final char WILDCARD_CHAR = '*'; |
| 47 | + |
61 | 48 | public static int ladderLength(String beginWord, String endWord, List<String> wordList) {
|
62 |
| - int L = beginWord.length(); |
63 |
| - Map<String, Set<String>> transformedToOriginalWordMap = new HashMap<>(); |
64 |
| - Queue<Pair<String, Integer>> queue = new LinkedList<>(); |
| 49 | + Map<String, Set<String>> allComboDict = preprocessWords(wordList); |
| 50 | + return performBFS(beginWord, endWord, allComboDict); |
| 51 | + } |
65 | 52 |
|
| 53 | + private static Map<String, Set<String>> preprocessWords(List<String> wordList) { |
| 54 | + Map<String, Set<String>> allComboDict = new HashMap<>(); |
| 55 | + int L = wordList.get(0).length(); |
66 | 56 | wordList.forEach(word -> {
|
67 |
| - String transformedWord; |
68 |
| - for (int i = 0; i < L; i++) { |
69 |
| - transformedWord = word.substring(0, i) + "*" + word.substring(i + 1, L); |
70 |
| - transformedToOriginalWordMap.putIfAbsent(transformedWord, new HashSet<>()); |
71 |
| - transformedToOriginalWordMap.get(transformedWord).add(word); |
72 |
| - } |
73 |
| - } |
74 |
| - ); |
| 57 | + for (int i = 0; i < L; i++) { |
| 58 | + String newWord = word.substring(0, i) + WILDCARD_CHAR + word.substring(i + 1, L); |
| 59 | + allComboDict.computeIfAbsent(newWord, k -> new HashSet<>()).add(word); |
| 60 | + } |
| 61 | + }); |
| 62 | + return allComboDict; |
| 63 | + } |
75 | 64 |
|
76 |
| - Set<String> visited = new HashSet<>(); |
| 65 | + private static int performBFS(String beginWord, String endWord, Map<String, Set<String>> allComboDict) { |
| 66 | + Queue<Pair<String, Integer>> queue = new LinkedList<>(); |
77 | 67 | queue.add(new Pair<>(beginWord, 1));
|
| 68 | + |
| 69 | + Set<String> visited = new HashSet<>(); |
78 | 70 | visited.add(beginWord);
|
79 | 71 |
|
80 | 72 | while (!queue.isEmpty()) {
|
81 |
| - Pair<String, Integer> currPair = queue.poll(); |
82 |
| - String word = currPair.getKey(); |
83 |
| - Integer level = currPair.getValue(); |
84 |
| - |
85 |
| - if (word.equals(endWord)) { |
86 |
| - return level; |
87 |
| - } |
88 |
| - |
89 |
| - String transformedWord; |
90 |
| - for (int i = 0; i < L; i++) { |
91 |
| - transformedWord = word.substring(0, i) + "*" + word.substring(i + 1, L); |
92 |
| - |
93 |
| - for (String originalWord : transformedToOriginalWordMap.getOrDefault(transformedWord, Collections.emptySet())) { |
94 |
| - if (!visited.contains(originalWord)) { |
95 |
| - queue.add(new Pair<>(originalWord, level + 1)); |
96 |
| - visited.add(originalWord); |
| 73 | + Pair<String, Integer> node = queue.remove(); |
| 74 | + String word = node.getKey(); |
| 75 | + int level = node.getValue(); |
| 76 | + for (int i = 0; i < word.length(); i++) { |
| 77 | + String newWord = word.substring(0, i) + WILDCARD_CHAR + word.substring(i + 1); |
| 78 | + for (String adjacentWord : allComboDict.getOrDefault(newWord, new HashSet<>())) { |
| 79 | + if (adjacentWord.equals(endWord)) { |
| 80 | + return level + 1; |
| 81 | + } |
| 82 | + if (!visited.contains(adjacentWord)) { |
| 83 | + visited.add(adjacentWord); |
| 84 | + queue.add(new Pair<>(adjacentWord, level + 1)); |
97 | 85 | }
|
98 | 86 | }
|
99 | 87 | }
|
100 | 88 | }
|
101 |
| - |
102 | 89 | return 0;
|
103 | 90 | }
|
104 | 91 |
|
105 |
| - /** |
106 |
| - * TODO: Optimized both end BFS solution |
107 |
| - * |
108 |
| - * @param beginWord |
109 |
| - * @param endWord |
110 |
| - * @param wordList |
111 |
| - * @return |
112 |
| - */ |
113 |
| - public static int ladderLengthOptimized(String beginWord, String endWord, List<String> wordList) { |
114 |
| - return -1; |
115 |
| - } |
116 |
| - |
117 | 92 | public static void main(String[] args) {
|
118 | 93 | assertEquals(5, ladderLength("hit", "cog", Arrays.asList("hot", "dot", "dog", "lot", "log", "cog")));
|
119 | 94 | assertEquals(0, ladderLength("hit", "cog", Arrays.asList("hot", "dot", "dog", "lot", "log")));
|
|
0 commit comments