-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path269. Alien Dictionary.js
253 lines (229 loc) · 6.63 KB
/
269. Alien Dictionary.js
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/**
There is a new alien language which uses the latin alphabet.
However, the order among letters are unknown to you. You receive a list of non-empty words
from the dictionary, where words are sorted lexicographically by the rules of this new language.
Derive the order of letters in this language.
Note:
You may assume all letters are in lowercase.
You may assume that if a is a prefix of b, then a must appear before b in the given dictionary.
If the order is invalid, return an empty string.
There may be multiple valid order of letters, return any one of them is fine.
*/
/**
* Algorithm: Graph: Topological Sort
* Note:
* Topological Ordering: In directed graph, the graph is constructed following a specific order between parents and children
* ex: Lexical order:
* B -- D G
* / \ /
* A F
* \ / \
* C -- E H -- I
*
* Topological sort algo can find a topological ordering in O(V + E) time
* Note:
* a. Topological orderings are not unique
* b. The graph have to be DAG (Directed Acyclic Graphs) aka. no cycle
* c. If there is a cycle in the graph, no topological ordering
* d. THe root can be any node that inDegree == 0
*/
/**
* BFS solution
*/
/**
* @param {string[]} words
* @return {string}
*/
var alienOrder = function(words) {
if (typeof words === "undefined" || words.length === 0) return "";
let result = "";
let nodeDependency = new Map(); // <char, Set(char)>
let set = new Set(); // <set(char used in words)>
let inDegree = new Array(26).fill(0); // <c.charCodeAt(0) - 97, count>
// Add all chars used in words to set
for (let word of words) {
for (let char of word) {
set.add(char);
}
}
// Build dependency graph
for (let i = 0; i < words.length - 1; i += 1) {
let currWord = words[i];
let nextWord = words[i+1];
let len = Math.min(currWord.length, nextWord.length);
for (let j = 0; j < len; j += 1) {
let currChar = currWord[j];
let nextChar = nextWord[j];
if (currChar !== nextChar) {
// Build neighbor set and update inDegree
let neighborSet = nodeDependency.has(currChar) ? nodeDependency.get(currChar) : new Set();
if (!neighborSet.has(nextChar)) { // avoid duplicates
neighborSet.add(nextChar);
nodeDependency.set(currChar, neighborSet);
let index = nextChar.charCodeAt(0) - 97;
inDegree[index] += 1;
}
break;
}
}
}
// Insert the nodes which have no parents
let queue = [];
for (let char of set) {
let index = char.charCodeAt(0) - 97;
if (inDegree[index] === 0) {
queue.push(char);
}
}
// BFS
while (queue.length !== 0) {
let char = queue.shift();
result += char;
if (!nodeDependency.has(char)) continue;
for (let nextChar of nodeDependency.get(char)) {
// decrease the degree and insert the new node which have no parents
let index = nextChar.charCodeAt(0) - 97;
inDegree[index] -= 1;
if (inDegree[index] === 0) queue.push(nextChar);
}
}
// Avoid the loop
if (result.length !== set.size) return "";
return result;
};
const NODE_STATUS = {
UNVISITED: 0,
IN_PROGRESS: 1,
VISITED: 2
}
/**
* 2020/12/18 Update cycle detection approach
* @param {string[]} words
* @return {string}
*/
function alienOrder(words) {
if (words.length === 0) return '';
// use map because it's iterable
const graph = {}; // only char shown in words can be valid node
const statuses = {};
// initialize empty edge for each node
for (const word of words) {
for (const char of word) {
graph[char] = [];
statuses[char] = NODE_STATUS.UNVISITED;
}
}
// add edge to graph
// compare two adjacent word, order is only determined when first pair char difference appears
for (let i = 0; i < words.length - 1; i++) {
const word = words[i];
const next_word = words[i+1];
if (word.length > next_word.length && word.slice(0, next_word.length) === next_word) {
return ''; // invalid 'abc', 'ab' case, cannot compare 'c' with ''
}
let min_length = Math.min(word.length, next_word.length);
for (let j = 0; j < min_length; j++) {
if (word[j] !== next_word[j]) {
graph[word[j]].push(next_word[j]);
break;
}
}
}
let result = [];
for (const node in graph) {
if (hasCycle(node, graph, statuses, result)) return '';
}
return result.reverse().join('');
};
function hasCycle(node, graph, statuses, result) {
if (statuses[node] === NODE_STATUS.VISITED) return false;
if (statuses[node] === NODE_STATUS.IN_PROGRESS) return true;
statuses[node] = NODE_STATUS.IN_PROGRESS;
for (const next_node of graph[node]) {
if (hasCycle(next_node, graph, statuses, result)) return true;
}
statuses[node] = NODE_STATUS.VISITED;
result.push(node);
return false;
}
/**
* 2020/12/18 Update BFS approach
* @param {string[]} words
* @return {string}
*/
function alienOrder(words) {
if (words.length === 0) return '';
let result = [];
const graph = {};
const in_degrees = {};
for (const word of words) {
for (const char of word) {
graph[char] = [];
in_degrees[char] = 0;
}
}
for (let i = 0; i < words.length - 1; i++) {
const word = words[i];
const next_word = words[i+1];
if (word.length > next_word.length && word.slice(0, next_word.length) === next_word) return '';
let min_length = Math.min(word.length, next_word.length);
for (let j = 0; j < min_length; j++) {
if (word[j] !== next_word[j]) {
in_degrees[next_word[j]]++;
graph[word[j]].push(next_word[j]);
break;
}
}
}
topologicalSort(graph, in_degrees, result);
return result.length === Object.keys(graph).length ? result.join('') : '';
};
function topologicalSort(graph, in_degrees, result) {
const queue = [];
for (const node in in_degrees) {
if (in_degrees[node] === 0) queue.push(node);
}
while (queue.length !== 0) {
let q_length = queue.length;
for (let i = 0; i < q_length; i++) {
const node = queue.shift();
result.push(node);
for (const next_node of graph[node]) {
in_degrees[next_node]--;
if (in_degrees[next_node] === 0) queue.push(next_node);
}
}
}
return result;
}
let input0 = [
"ac",
"ab",
"b"
]
console.log(alienOrder(input0)); // "acb"
let input1 = [
"wrt",
"wrf",
"er",
"ett",
"rftt",
"te"
];
console.log(alienOrder(input1)); // "wertf"
let input2 = [
"z",
"x"
];
console.log(alienOrder(input2)); // "zx"
let input3 = [
"z",
"x",
"z"
];
console.log(alienOrder(input3)); // "" (Explanation: The order is invalid, so return "")
let input4 = [
"z",
"z"
];
console.log(alienOrder(input4)); // "z"