- We are given a list of words that are already sorted according to some alien dictionary, and we need to reconstruct a valid ordering of characters consistent with this sorting.
-
We model this as a graph / topological ordering problem over at most 26 lowercase letters:
-
We use a fixed-size
dependenciesMap[alphabetIndex], where each entry is a set of characters that must come before the character corresponding to that index.
Here,alphabetIndexrepresents the zero-based position of a lowercase English letter (e.g.,'a' β 0,'b' β 1, β¦,'z' β 25). -
We use a
state[alphabetIndex]array mapped in the same way β each entry corresponds to the samealphabetIndexas independenciesMapand tracks the DFS visit status of that character:- 0 β unvisited
- 1 β currently visiting (part of the current DFS path)
- 2 β fully processed (all dependencies resolved)
-
We maintain a
resultlist where we append characters once all their dependencies are resolved.
-
We use a fixed-size
-
Building the dependency rules from adjacent words:
- The alien dictionary guarantees that the given words are already sorted according to the unknown alphabet order. Therefore, by comparing only adjacent words, we can deduce all necessary ordering relationships between characters β since if two words differ at an index, their relative order already reflects how their first differing characters must be ordered in the alien language.
- For each pair of adjacent words, we scan from left to right to find the first position where they differ. Characters after this position have no effect on the ordering and are ignored for this comparison.
-
If we find differing characters
firstCharandsecondCharat indexi, it meansfirstCharmust come beforesecondCharin the alien order. This rule is recorded independenciesMapby addingfirstCharas a dependency ofsecondChar. - Once this first differing position is found, any remaining characters in those words do not add new constraints, since their relative order is already determined by the earlier difference.
-
If no difference is found and the first word is longer than the second (for example,
"abc"before"ab"), this violates lexicographic ordering rules β such a case is invalid, and we return an empty result immediately.
-
DFS over characters (dependency resolution & cycle detection):
-
For each character
chwe encountered in dfs:-
If
state[ch] = 1, it meanschis already on the current DFS path β a cycle is detected. In this case, we prune this branch and returnfalseto signal failure. -
If
state[ch] = 2, it was already fully processed in a previous DFS call, so we skip reprocessing and returntrue. -
Otherwise, we begin processing
ch:-
We set
state[ch] = 1(βcurrently visitingβ) to mark it as part of the current DFS path. Any revisit to a character withstate = 1along this path indicates a cycle, so we prune the DFS branch and returnfalseto signal failure. -
For each dependency
preindependenciesMap[ch]:-
We recursively perform DFS on
preto ensure that all characters which must come beforechare fully resolved first.-
If any recursive DFS call returns
false(indicating a cycle), we returnfalseto propagate that failure upward without further exploration.
-
If any recursive DFS call returns
-
We recursively perform DFS on
-
After all dependencies of
chare successfully processed, we setstate[ch] = 2(fully processed) and appendchto theresultlist, returningtrueto indicate successful completion of that DFS branch.
-
We set
-
If
-
For each character
-
Global traversal over all characters:
-
We iterate over all characters appearing in the input words and invoke DFS on each
(the DFS function itself handles skipping characters already marked as fully processed
(
state = 2) immediately).-
If any DFS call returns
false, it means a cycle was detected, so no valid alien ordering exists and the function returns an empty string.
-
If any DFS call returns
-
If no DFS call fails, meaning that all reachable characters are processed without conflicts,
the collected
resultsequence contains the alien characters in their correct order. We then join its contents into a string and return it.
-
We iterate over all characters appearing in the input words and invoke DFS on each
(the DFS function itself handles skipping characters already marked as fully processed
(
Foreign Dictionary - JS Solution
JSSolution code
const CURRENTLY_VISITING = 'currently_visiting', CURRENTLY_VISITING_COLOR = 'orange';
const DEPENDENCY = 'dependency', DEPENDENCY_COLOR = 'purple';
const CURRENT_INPUT = 'current_input', CURRENT_INPUT_COLOR = 'blue';
function visMainFunction(wordsInput) {
initClassProperties();
explanationAndColorSchemaNotifications();
startBatch();
const words = new VisArray2D(...wordsInput.map(word => new VisArray(...word)));
const dependenciesMap = VisArray.from({ length: 26 }, () => new VisSet()).options({ labelExtractionPolicy: s => [...s].join(',') });
const state = new VisArray(26).fill(0); // 0 = unvisited, 1 = visiting, 2 = visited
const result = new VisArray();
endBatch();
// Build dependenciesMap by comparing adjacent words
startPartialBatch();
for (let firstWordIndex = 0; firstWordIndex < words.length - 1; firstWordIndex++) {
const firstWord = words[firstWordIndex].createVis(false);
const secondWord = words[firstWordIndex + 1].createVis(false);
const comparisonLimit = Math.min(firstWord.length, secondWord.length);
let differenceFound = false;
for (let charIndexInWord = 0; charIndexInWord < comparisonLimit; charIndexInWord++) {
const firstChar = firstWord[charIndexInWord];
const secondChar = secondWord[charIndexInWord];
const alphabetIndexOfSecondChar = charToIndex(secondChar);
if (firstChar !== secondChar) {
const firstCharVM = words.makeVisManagerForIndexPair(firstWordIndex, charIndexInWord).addClass(CURRENT_INPUT);
const secondCharVM = words.makeVisManagerForIndexPair(firstWordIndex + 1, charIndexInWord).addClass(CURRENT_INPUT);
notification(`
π Comparing adjacent words <b>"${firstWord}"</b> and <b>"${secondWord}"</b>:<br>
Found the first differing position at index <b>${charIndexInWord}</b>,
meaning ${makeColoredSpan(firstChar, DEPENDENCY_COLOR)} must come before ${makeColoredSpan(secondChar, CURRENTLY_VISITING_COLOR)} in the alien order.<br>
We record this rule by adding ${makeColoredSpan(firstChar, DEPENDENCY_COLOR)} as a dependency of
${makeColoredSpan(secondChar, CURRENT_INPUT_COLOR)} in
<code>dependenciesMap[${alphabetIndexOfSecondChar}]</code>,
where ${makeColoredSpan(`alphabet index ${alphabetIndexOfSecondChar} corresponds to '${secondChar}'`, CURRENT_INPUT_COLOR)}.
`);
dependenciesMap[alphabetIndexOfSecondChar].add(firstChar);
dependenciesMap[alphabetIndexOfSecondChar] = dependenciesMap[alphabetIndexOfSecondChar];
differenceFound = true;
firstCharVM.removeClass(CURRENT_INPUT);
secondCharVM.removeClass(CURRENT_INPUT);
break;
}
}
if (!differenceFound && firstWord.length > secondWord.length) {
notification(`
β ${makeColoredSpan('Invalid ordering detected!', 'red')}
Word <b>"${firstWord}"</b> is a complete prefix of <b>"${secondWord}"</b> but appears earlier in the list β
which violates the alien lexicographic rules.
Such a case makes the ordering impossible, so we stop and return an empty result.
`);
return '';
}
}
endBatch();
const dfs = (ch) => {
const charIndex = charToIndex(ch);
setCurrentlyVisiting(ch, dependenciesMap, state);
if (state[charIndex] === 1) {
notification(`
β ${makeColoredSpan('Cycle detected!', 'red')}
Character ${makeColoredSpan(ch, CURRENTLY_VISITING_COLOR)} is already marked as "currently visiting" (<code>state = 1</code>),
meaning weβve encountered it again while still exploring its dependencies β
indicating a circular dependency.
This makes the current ordering invalid, so we <b>prune this DFS path</b> and return <code>false</code> to indicate failure.
`);
return false;
}
if (state[charIndex] === 2) {
notification(`
β
${makeColoredSpan(ch, 'green')} is already marked as fully processed (<code>state = 2</code>) β
all its dependencies were resolved earlier, so we skip reprocessing it
and return <code>true</code> to indicate that no failure was detected along this path.
`);
return true;
}
const dependencies = dependenciesMap[charIndex].options({ label: `"${ch}" dependencies` }).createVis();
notification(`
π Visiting ${makeColoredSpan(ch, CURRENTLY_VISITING_COLOR)} β
marking it as ${makeColoredSpan('currently visiting', CURRENTLY_VISITING_COLOR)} (<code>state = 1</code>)
so that any revisit to it will indicate a cycle.
Now processing its dependencies listed in <code>dependenciesMap[${charIndex}]</code>,
where ${charIndex} is the alphabet index of ${makeColoredSpan(ch, CURRENTLY_VISITING_COLOR)}.
`);
state[charIndex] = 1;
for (const pre of dependenciesMap[charIndex]) {
const vm = dependencies.makeVisManagerForMember(pre).addClass(DEPENDENCY);
notification(`
π Processing dependency ${makeColoredSpan(pre, DEPENDENCY_COLOR)},
which must appear before ${makeColoredSpan(ch, CURRENTLY_VISITING_COLOR)} in the alien order.
Performing DFS on ${makeColoredSpan(pre, DEPENDENCY_COLOR)} to ensure all of its own dependencies are resolved first.
`);
if (!dfs(pre)) {
dependencies.destroyVis();
return false;
}
setCurrentlyVisiting(ch, dependenciesMap, state);
vm.removeClass(DEPENDENCY);
}
notification(`
β
${makeColoredSpan(ch, 'green')} has no remaining unresolved dependencies β
marking it as fully processed (<code>state = 2</code>) and adding it to the result list.
Returning <code>true</code> to indicate successful completion of its DFS exploration.
`);
state[charIndex] = 2;
result.push(ch);
dependencies.destroyVis();
return true;
};
notification(`
π <b>Starting DFS traversal for all characters appearing in the input words.</b><br>
For each character <code>ch</code> found in the word list, we perform a recursive DFS
to resolve its correct ordering according to the dependencies recorded in <code>dependenciesMap</code>.<br>
Each DFS call explores the characterβs dependencies recursively until all preceding characters
are resolved or a cycle is detected.<br>
Characters already fully processed will be skipped automatically, while any cycle encountered
will terminate the traversal with an invalid result.
`);
// Run DFS for all characters seen in the words
for (let wordIndex = 0; wordIndex < words.length; wordIndex++) {
const word = words[wordIndex].createVis(false);
for (let charIndex = 0; charIndex < word.length; charIndex++) {
startPartialBatch();
const ch = word[charIndex];
const vm = words.makeVisManagerForIndexPair(wordIndex, charIndex).addClass(CURRENT_INPUT);
if (!dfs(ch)) {
notification(`
β ${makeColoredSpan('Cycle detected!', 'red')}
DFS reported a circular dependency involving ${makeColoredSpan(ch, CURRENTLY_VISITING_COLOR)}.
No valid character ordering exists, so we return an empty result.
`);
return '';
}
vm.removeClass(CURRENT_INPUT);
endBatch();
}
}
notification(`π <b>Finished!</b> A valid alien character order has been determined: <code>${result.join('')}</code>. β
`);
return result.join('');
}
function charToIndex(ch) {
return ch.charCodeAt(0) - 97;
}
// Visual helpers
function initClassProperties() {
setClassProperties(DEPENDENCY, { backgroundColor: DEPENDENCY_COLOR });
setClassProperties(CURRENTLY_VISITING, { borderColor: CURRENTLY_VISITING_COLOR });
setClassProperties(CURRENT_INPUT, { backgroundColor: CURRENT_INPUT_COLOR });
}
let lastCurrentCharIndex = -1;
function setCurrentlyVisiting(ch, dependenciesMap, state) {
startBatch();
if (lastCurrentCharIndex > -1) {
dependenciesMap.makeVisManagerForIndex(lastCurrentCharIndex).removeClass(CURRENTLY_VISITING);
state.makeVisManagerForIndex(lastCurrentCharIndex).removeClass(CURRENTLY_VISITING);
}
const charIndex = charToIndex(ch);
dependenciesMap.makeVisManagerForIndex(charIndex).addClass(CURRENTLY_VISITING);
state.makeVisManagerForIndex(charIndex).addClass(CURRENTLY_VISITING);
lastCurrentCharIndex = charIndex;
endBatch();
}
function explanationAndColorSchemaNotifications() {
explanationNotification();
notification(`
π¨ <b>Visual Cues (Color Schema)</b>
<ul>
<li>
${makeColoredSpan(CURRENT_INPUT_COLOR + ' background', CURRENT_INPUT_COLOR)} β characters currently highlighted while comparing <b>adjacent words</b> and during the <b>global DFS iteration</b>.
</li>
<li>
${makeColoredSpan(CURRENTLY_VISITING_COLOR + ' border', CURRENTLY_VISITING_COLOR)} β the character <b>currently visiting</b> in DFS (<code>state = 1</code>), i.e., on the active recursion path.
</li>
<li>
${makeColoredSpan(DEPENDENCY_COLOR + ' background', DEPENDENCY_COLOR)} β a <b>dependency</b> character (<code>pre</code>) that must appear before the current DFS character and is being processed recursively.
</li>
</ul>
`);
}Solution explanation
Solution explanation
Approach
Time Complexity
Let N be the total number of characters across all input words, and let C be the number of distinct letters (β€ 26).
Building the dependency rules from adjacent words takes O(N) time.
During DFS, each character is explored only once (aside from constant-time checks for those already fully processed
or currently being visited on the DFS path), and the maximum recursion depth of any DFS call is bounded by C.
Thus, the total DFS workload across all unique characters (excluding immediate returns) is O(CΒ²),
and across all characters (including DFS calls that immediately return) is O(N + CΒ²) in the worst case.
Combining both phases, the overall running time is O(N + CΒ²), which simplifies to O(N) since C is a constant (β€ 26).
Space Complexity
We use a fixed-size dependenciesMap array of length 26, where each entry is a set holding characters that must appear before the corresponding alphabet letter.
This structure can contain at most 26 Γ 26 relationships β a constant bound independent of input size.
The state array and result list are also fixed-size, each tracking at most 26 characters.
No additional data structures scale with the number of input words or characters beyond these constant-sized arrays.
Hence, the overall space complexity is O(1) with respect to the input size.
Visucodize editorial solution titled JS Solution for NeetCode's Foreign Dictionary coding problem. Includes code and may include explanation. You can animate the code step by step using the provided input by clicking the run button, or fork it locally to update the code and input for custom visualization. View the original problem at https://neetcode.io/problems/foreign-dictionary.