-
Notifications
You must be signed in to change notification settings - Fork 43
/
LongestStringChain.java
166 lines (146 loc) · 5.87 KB
/
LongestStringChain.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package LeetCodeJava.DynamicProgramming;
// https://leetcode.com/problems/longest-string-chain/description/
import java.util.*;
/**
*1048. Longest String Chain
* Medium
* Topics
* Companies
* Hint
* You are given an array of words where each word consists of lowercase English letters.
*
* wordA is a predecessor of wordB if and only if we can insert exactly one letter anywhere in wordA without changing the order of the other characters to make it equal to wordB.
*
* For example, "abc" is a predecessor of "abac", while "cba" is not a predecessor of "bcad".
* A word chain is a sequence of words [word1, word2, ..., wordk] with k >= 1, where word1 is a predecessor of word2, word2 is a predecessor of word3, and so on. A single word is trivially a word chain with k == 1.
*
* Return the length of the longest possible word chain with words chosen from the given list of words.
*
* Example 1:
*
* Input: words = ["a","b","ba","bca","bda","bdca"]
* Output: 4
* Explanation: One of the longest word chains is ["a","ba","bda","bdca"].
* Example 2:
*
* Input: words = ["xbc","pcxbcf","xb","cxbc","pcxbc"]
* Output: 5
* Explanation: All the words can be put in a word chain ["xb", "xbc", "cxbc", "pcxbc", "pcxbcf"].
* Example 3:
*
* Input: words = ["abcd","dbqca"]
* Output: 1
* Explanation: The trivial word chain ["abcd"] is one of the longest word chains.
* ["abcd","dbqca"] is not a valid word chain because the ordering of the letters is changed.
*
*
* Constraints:
*
* 1 <= words.length <= 1000
* 1 <= words[i].length <= 16
* words[i] only consists of lowercase English letters.
*
*/
public class LongestStringChain {
// V0
// TODO : implement
// public int longestStrChain(String[] words) {
// }
// V1-1
// IDEA : DFS + Memoization | Increasing Word Length:
// https://leetcode.com/problems/longest-string-chain/solutions/2153004/explaining-three-approaches-java/
private Map<Integer, List<String>> wordLengthMap;
private Map<String, Integer> memo;
public int longestStrChain_1(String[] words) {
// store each word with its corresponding length
wordLengthMap = new HashMap<>();
for (String word: words) {
wordLengthMap.putIfAbsent(word.length(), new ArrayList<>());
wordLengthMap.get(word.length()).add(word);
}
int maxPath = 1;
memo = new HashMap<>();
for (String word: words)
maxPath = Math.max(maxPath, dfs(word));
return maxPath;
}
private int dfs(String word) {
if (!wordLengthMap.containsKey(word.length() + 1)) return 1; // if there are no words of the next length, we're done with this path.
if (memo.containsKey(word)) return memo.get(word); // if we're computed this word before, return the result.
int maxPath = 0;
// for each word, find all words which are 1 letter longer and see if they are valid successors.
List<String> nextWords = wordLengthMap.get(word.length() + 1);
for (String nextWord: nextWords)
if (isOneOff(word, nextWord))
maxPath = Math.max(maxPath, dfs(nextWord));
memo.put(word, maxPath + 1); // store our result
return memo.get(word);
}
// returns true if two strings differ by no more than 1 letter
private boolean isOneOff(String a, String b) {
int count = 0;
for (int i=0, j=0; i<b.length() && j<a.length() && count <= 1; i++) {
if (a.charAt(j) != b.charAt(i)) count++;
else j++;
}
return count <= 1;
}
// V1-2
// IDEA : DFS + Memoization | Decreasing Word Length:
// https://leetcode.com/problems/longest-string-chain/solutions/2153004/explaining-three-approaches-java/
// private Set<String> wordDict;
// private Map<String, Integer> memo;
//
// public int longestStrChain(String[] words) {
// wordDict = new HashSet<>();
// Collections.addAll(wordDict, words); // adding all words to a set for constant look-up
//
// int maxPath = 1;
// memo = new HashMap<>();
// for (String word: words)
// maxPath = Math.max(maxPath, dfs(word));
//
// return maxPath;
// }
//
// private int dfs(String word) {
// if (memo.containsKey(word)) return memo.get(word); // if we're computed this word before, return the result.
//
// StringBuilder sb = new StringBuilder(word);
// int maxPath = 0;
// // delete each character, check if that's a valid word in the set, add the character back and continue
// for (int i=0; i<word.length(); i++) {
// sb.deleteCharAt(i);
// String prevWord = sb.toString();
// if (wordDict.contains(prevWord))
// maxPath = Math.max(maxPath, dfs(prevWord));
// sb.insert(i, word.charAt(i));
// }
//
// memo.put(word, maxPath + 1); // store the result
// return memo.get(word);
// }
// V1-3
// IDEA : DP | Decreasing Word Length:
// https://leetcode.com/problems/longest-string-chain/solutions/2153004/explaining-three-approaches-java/
public int longestStrChain_1_3(String[] words) {
Arrays.sort(words, (String a, String b) -> a.length() - b.length()); // sort by length
Map<String, Integer> dp = new HashMap<>();
int maxPath = 1;
// same idea behind the previous approach but performed iteratively.
for (String word: words) {
int currLength = 1;
StringBuilder sb = new StringBuilder(word);
for (int i=0; i<word.length(); i++) {
sb.deleteCharAt(i);
String prevWord = sb.toString();
currLength = Math.max(currLength, dp.getOrDefault(prevWord, 0) + 1);
sb.insert(i, word.charAt(i));
}
dp.put(word, currLength);
maxPath = Math.max(maxPath, currLength);
}
return maxPath;
}
// V2
}