LeetCode 953 - Verifying an Alien Dictionary
This problem asks us to verify whether a list of words is sorted according to a custom alphabet order, instead of the normal English alphabetical order. In normal lexicographical ordering, characters are compared from left to right using the standard alphabet.
Difficulty: 🟢 Easy
Topics: Array, Hash Table, String
Solution
Problem Understanding
This problem asks us to verify whether a list of words is sorted according to a custom alphabet order, instead of the normal English alphabetical order.
In normal lexicographical ordering, characters are compared from left to right using the standard alphabet. Here, the alien language still uses lowercase English letters, but the relative order of those letters may be completely different. The order string tells us the ranking of each character in this alien language.
The input consists of:
words, an array of strings written in the alien languageorder, a string of length 26 that defines the alien alphabet ordering
The goal is to determine whether the words are already sorted according to this custom lexicographical order.
Lexicographical comparison works exactly like dictionary comparison:
- Compare characters one by one from left to right
- The first differing character determines which word is smaller
- If all compared characters are identical, then the shorter word is considered smaller
For example:
"app"comes before"apple""apple"does not come before"app"
The constraints are relatively small:
- At most 100 words
- Each word has length at most 20
- Only lowercase English letters are used
These limits tell us that performance is not extremely critical, but we should still design a clean and efficient solution. Since the alphabet size is fixed at 26, building a lookup table for character rankings is very cheap.
Several edge cases are especially important:
- One word may be a prefix of another
- The first differing character may appear very late in the strings
- Adjacent words may be identical
- The alien ordering may differ drastically from normal English order
A naive implementation can easily fail on prefix cases like ["apple", "app"], where all shared characters match but the longer word incorrectly appears first.
Approaches
Brute Force Approach
A brute force solution would simulate lexicographical comparison directly for every adjacent pair of words.
For each pair:
- Compare characters one by one
- Find the first differing character
- Determine ordering using the alien alphabet
- If no differing character exists, compare word lengths
To determine character ordering, the brute force method could repeatedly search through the order string using operations like order.index(char) whenever characters are compared.
This works correctly because lexicographical ordering only depends on the first differing character. However, repeatedly scanning the order string is inefficient because every character lookup costs O(26) time.
Even though the constraints are small, this repeated searching is unnecessary.
Optimal Approach
The key insight is that character comparisons happen frequently, so we should preprocess the alien alphabet into a direct lookup structure.
We create a hash map:
character -> rank
For example:
h -> 0
l -> 1
a -> 2
...
Now each character comparison becomes O(1) instead of scanning the entire alphabet string.
We then compare every adjacent pair of words lexicographically using these ranks.
This gives a clean and efficient solution.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(N × M × 26) | O(1) | Repeatedly scans the order string for character ranking |
| Optimal | O(N × M) | O(26) | Uses hash map for constant time character ranking |
Where:
N= number of wordsM= maximum word length
Algorithm Walkthrough
- Create a hash map called
rank.
This map stores the position of each character in the alien alphabet.
For example:
rank['h'] = 0
rank['l'] = 1
rank['a'] = 2
This allows constant time comparisons between characters. 2. Iterate through all adjacent pairs of words.
Since a sorted list only requires every neighboring pair to be correctly ordered, we compare:
words[0] vs words[1]
words[1] vs words[2]
...
- Compare the two words character by character.
Use a loop over the minimum length of the two words.
At each position:
-
If characters are equal, continue
-
If characters differ:
-
Compare their alien ranks
-
If the first word's character has a larger rank, return
False -
Otherwise, the pair is correctly ordered, so stop checking this pair
- Handle the prefix case.
If all compared characters match, then ordering depends entirely on length.
Example:
"app" < "apple"
If the first word is longer after all shared characters match, the ordering is invalid.
5. If all adjacent pairs are valid, return True.
Why it works
Lexicographical ordering is determined entirely by the first differing character between two strings. If all characters match up to the shorter word's length, then the shorter word must come first.
The algorithm directly implements this rule for every adjacent pair of words. Since a sequence is sorted if and only if every neighboring pair is sorted, checking all adjacent pairs guarantees correctness.
Python Solution
from typing import List
class Solution:
def isAlienSorted(self, words: List[str], order: str) -> bool:
rank = {char: index for index, char in enumerate(order)}
for i in range(len(words) - 1):
word1 = words[i]
word2 = words[i + 1]
found_difference = False
for j in range(min(len(word1), len(word2))):
if word1[j] != word2[j]:
if rank[word1[j]] > rank[word2[j]]:
return False
found_difference = True
break
if not found_difference and len(word1) > len(word2):
return False
return True
The implementation begins by constructing the rank dictionary. This preprocessing step converts the alien alphabet into constant time lookups.
The outer loop iterates through adjacent pairs of words because verifying every neighboring pair is sufficient to confirm the entire list is sorted.
For each pair, the inner loop compares characters position by position.
If the characters differ, their alien ranks determine ordering immediately. If the first word contains a character with a larger rank, the list is not sorted, so the function returns False.
The variable found_difference tracks whether a mismatch was encountered. This is important for handling prefix cases correctly. If no differing character exists and the first word is longer, then the ordering is invalid.
If all pairs pass the checks, the function returns True.
Go Solution
func isAlienSorted(words []string, order string) bool {
rank := make(map[byte]int)
for i := 0; i < len(order); i++ {
rank[order[i]] = i
}
for i := 0; i < len(words)-1; i++ {
word1 := words[i]
word2 := words[i+1]
foundDifference := false
minLength := len(word1)
if len(word2) < minLength {
minLength = len(word2)
}
for j := 0; j < minLength; j++ {
if word1[j] != word2[j] {
if rank[word1[j]] > rank[word2[j]] {
return false
}
foundDifference = true
break
}
}
if !foundDifference && len(word1) > len(word2) {
return false
}
}
return true
}
The Go implementation follows the same logic as the Python version.
One notable difference is that Go strings are indexed as bytes, which works perfectly here because the problem guarantees lowercase English letters only.
Go does not provide a built in min() function for integers in older versions, so the minimum length is computed manually.
The hash map uses map[byte]int because characters are represented as bytes.
Worked Examples
Example 1
words = ["hello", "leetcode"]
order = "hlabcdefgijkmnopqrstuvwxyz"
Step 1: Build rank map
| Character | Rank |
|---|---|
| h | 0 |
| l | 1 |
| a | 2 |
| b | 3 |
| c | 4 |
Remaining characters continue similarly.
Step 2: Compare "hello" and "leetcode"
| Position | word1 | word2 | Comparison |
|---|---|---|---|
| 0 | h | l | h < l |
Since rank['h'] < rank['l'], the ordering is valid.
Result:
True
Example 2
words = ["word", "world", "row"]
order = "worldabcefghijkmnpqstuvxyz"
Compare "word" and "world"
| Position | word1 | word2 | Comparison |
|---|---|---|---|
| 0 | w | w | equal |
| 1 | o | o | equal |
| 2 | r | r | equal |
| 3 | d | l | d > l |
Since d comes after l in the alien alphabet, ordering is invalid.
Result:
False
Example 3
words = ["apple", "app"]
order = "abcdefghijklmnopqrstuvwxyz"
Compare "apple" and "app"
| Position | word1 | word2 | Comparison |
|---|---|---|---|
| 0 | a | a | equal |
| 1 | p | p | equal |
| 2 | p | p | equal |
No differing character exists.
Now compare lengths:
| Word | Length |
|---|---|
| apple | 5 |
| app | 3 |
The first word is longer, so ordering is invalid.
Result:
False
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(N × M) | Each character of each adjacent word pair is compared at most once |
| Space | O(26) | The rank map stores one entry per lowercase letter |
The preprocessing step for the alien alphabet costs only O(26), which is effectively constant.
The main work comes from comparing adjacent words. In the worst case, every character of every word participates in comparisons, giving O(N × M) complexity.
The extra space usage is constant because the alphabet size never exceeds 26 letters.
Test Cases
from typing import List
class Solution:
def isAlienSorted(self, words: List[str], order: str) -> bool:
rank = {char: index for index, char in enumerate(order)}
for i in range(len(words) - 1):
word1 = words[i]
word2 = words[i + 1]
found_difference = False
for j in range(min(len(word1), len(word2))):
if word1[j] != word2[j]:
if rank[word1[j]] > rank[word2[j]]:
return False
found_difference = True
break
if not found_difference and len(word1) > len(word2):
return False
return True
solution = Solution()
assert solution.isAlienSorted(
["hello", "leetcode"],
"hlabcdefgijkmnopqrstuvwxyz"
) == True # basic valid ordering
assert solution.isAlienSorted(
["word", "world", "row"],
"worldabcefghijkmnpqstuvxyz"
) == False # differing character causes invalid order
assert solution.isAlienSorted(
["apple", "app"],
"abcdefghijklmnopqrstuvwxyz"
) == False # prefix edge case
assert solution.isAlienSorted(
["app", "apple"],
"abcdefghijklmnopqrstuvwxyz"
) == True # shorter prefix first is valid
assert solution.isAlienSorted(
["a", "b", "c"],
"abcdefghijklmnopqrstuvwxyz"
) == True # simple sorted sequence
assert solution.isAlienSorted(
["c", "b", "a"],
"abcdefghijklmnopqrstuvwxyz"
) == False # reverse sorted sequence
assert solution.isAlienSorted(
["same", "same"],
"abcdefghijklmnopqrstuvwxyz"
) == True # identical adjacent words
assert solution.isAlienSorted(
["z", "x"],
"zyxwvutsrqponmlkjihgfedcba"
) == True # custom reverse alphabet
assert solution.isAlienSorted(
["aa", "aaa"],
"abcdefghijklmnopqrstuvwxyz"
) == True # prefix with increasing length
assert solution.isAlienSorted(
["aaa", "aa"],
"abcdefghijklmnopqrstuvwxyz"
) == False # invalid prefix ordering
| Test | Why |
|---|---|
["hello","leetcode"] |
Verifies standard valid alien ordering |
["word","world","row"] |
Verifies detection of invalid character ordering |
["apple","app"] |
Tests critical prefix edge case |
["app","apple"] |
Verifies valid prefix ordering |
["a","b","c"] |
Tests simple increasing order |
["c","b","a"] |
Tests clearly invalid order |
["same","same"] |
Verifies identical words are accepted |
["z","x"] with reversed alphabet |
Tests non standard alphabet ranking |
["aa","aaa"] |
Valid prefix relationship |
["aaa","aa"] |
Invalid prefix relationship |
Edge Cases
Prefix Relationships
One of the most important edge cases occurs when one word is a prefix of another.
For example:
["apple", "app"]
All shared characters match, so the shorter word should come first. A naive implementation that only compares differing characters may incorrectly accept this ordering.
The implementation explicitly checks whether all compared characters matched and whether the first word is longer. If both are true, it returns False.
Identical Words
Adjacent words may be exactly the same.
For example:
["same", "same"]
No differing character exists, but the lengths are equal, so the ordering is valid.
The implementation handles this naturally because the prefix check only fails when the first word is strictly longer.
Custom Alphabet Order
The alien alphabet may differ completely from standard English ordering.
For example:
order = "zyxwvutsrqponmlkjihgfedcba"
In this language, 'z' is the smallest character.
A solution that accidentally relies on normal ASCII ordering would fail. The implementation avoids this issue entirely by always comparing characters using the precomputed rank map instead of direct character comparison.