205 lines
10 KiB
YAML
205 lines
10 KiB
YAML
title: Verifying an Alien Dictionary
|
|
slug: verifying-an-alien-dictionary
|
|
difficulty: easy
|
|
leetcode_id: 953
|
|
leetcode_url: https://leetcode.com/problems/verifying-an-alien-dictionary/
|
|
categories:
|
|
- arrays
|
|
- strings
|
|
- hash-tables
|
|
patterns:
|
|
- slug: two-pointers
|
|
is_optimal: true
|
|
|
|
function_signature: "def is_alien_sorted(words: list[str], order: str) -> bool:"
|
|
|
|
test_cases:
|
|
visible:
|
|
- input: { words: ["hello", "leetcode"], order: "hlabcdefgijkmnopqrstuvwxyz" }
|
|
expected: true
|
|
- input: { words: ["word", "world", "row"], order: "worldabcefghijkmnpqstuvxyz" }
|
|
expected: false
|
|
- input: { words: ["apple", "app"], order: "abcdefghijklmnopqrstuvwxyz" }
|
|
expected: false
|
|
hidden:
|
|
- input: { words: ["a"], order: "abcdefghijklmnopqrstuvwxyz" }
|
|
expected: true
|
|
- input: { words: ["a", "b", "c"], order: "abcdefghijklmnopqrstuvwxyz" }
|
|
expected: true
|
|
- input: { words: ["app", "apple"], order: "abcdefghijklmnopqrstuvwxyz" }
|
|
expected: true
|
|
- input: { words: ["a", "a"], order: "abcdefghijklmnopqrstuvwxyz" }
|
|
expected: true
|
|
|
|
description: |
|
|
In an alien language, surprisingly, they also use English lowercase letters, but possibly in a different `order`. The `order` of the alphabet is some permutation of lowercase letters.
|
|
|
|
Given a sequence of `words` written in the alien language, and the `order` of the alphabet, return `true` if and only if the given `words` are sorted lexicographically in this alien language.
|
|
|
|
constraints: |
|
|
- `1 <= words.length <= 100`
|
|
- `1 <= words[i].length <= 20`
|
|
- `order.length == 26`
|
|
- All characters in `words[i]` and `order` are English lowercase letters
|
|
|
|
examples:
|
|
- input: 'words = ["hello","leetcode"], order = "hlabcdefgijkmnopqrstuvwxyz"'
|
|
output: "true"
|
|
explanation: "As 'h' comes before 'l' in this language, the sequence is sorted."
|
|
- input: 'words = ["word","world","row"], order = "worldabcefghijkmnpqstuvxyz"'
|
|
output: "false"
|
|
explanation: "As 'd' comes after 'l' in this language, words[0] > words[1], hence the sequence is unsorted."
|
|
- input: 'words = ["apple","app"], order = "abcdefghijklmnopqrstuvwxyz"'
|
|
output: "false"
|
|
explanation: "The first three characters 'app' match, and the second string is shorter. According to lexicographical rules 'apple' > 'app', because 'l' > '∅' (the blank character is less than any other character)."
|
|
|
|
explanation:
|
|
intuition: |
|
|
Imagine you're a librarian in an alien library, tasked with checking if books are shelved in alphabetical order — but the alphabet itself is different!
|
|
|
|
In our familiar English, we know `a < b < c < ... < z`. But what if the order was `h < l < a < b < ...`? Then "hello" would come before "leetcode" because `h` precedes `l` in this alien alphabet.
|
|
|
|
The core insight is that **verifying sorted order is just comparing adjacent pairs**. If every consecutive pair of words is in the correct order, the entire list must be sorted. You don't need to compare every word with every other word — just check neighbours.
|
|
|
|
Think of it like dominos: if word 1 ≤ word 2, and word 2 ≤ word 3, and so on, then the whole sequence is sorted. One "out of order" pair breaks the chain.
|
|
|
|
The second key insight is that comparing two words follows the same logic as comparing strings in any language: compare character by character until you find a difference, then the word with the "smaller" character comes first. If one word is a prefix of another, the shorter one comes first.
|
|
|
|
approach: |
|
|
We solve this by **building a character priority map** and then **comparing adjacent word pairs**:
|
|
|
|
**Step 1: Build the character order map**
|
|
|
|
- Create a dictionary mapping each character to its position in the alien alphabet
|
|
- `order_map[char] = index` gives us O(1) lookups for character priority
|
|
- Example: if `order = "hlabcdefgijkmnopqrstuvwxyz"`, then `order_map['h'] = 0`, `order_map['l'] = 1`, etc.
|
|
|
|
|
|
|
|
**Step 2: Create a helper function to compare two words**
|
|
|
|
- Compare characters at the same position in both words
|
|
- If characters differ, return whether `word1[i] < word2[i]` according to `order_map`
|
|
- If all compared characters match but `word1` is longer, return `False` (prefix rule)
|
|
- If we exhaust the comparison without issues, return `True`
|
|
|
|
|
|
|
|
**Step 3: Check all adjacent pairs**
|
|
|
|
- Iterate through `words[0]` to `words[n-2]`
|
|
- For each pair `(words[i], words[i+1])`, verify they're in correct order
|
|
- If any pair is out of order, return `False` immediately
|
|
- If all pairs pass, return `True`
|
|
|
|
|
|
|
|
This approach efficiently validates the entire list by leveraging the transitive property of ordering.
|
|
|
|
common_pitfalls:
|
|
- title: Forgetting the Prefix Rule
|
|
description: |
|
|
When one word is a prefix of another (e.g., "app" vs "apple"), the shorter word must come first.
|
|
|
|
Many solutions correctly handle character-by-character comparison but forget this edge case. If you reach the end of the shorter word without finding a difference, you must check: is the first word shorter or equal in length? If `word1` is longer than `word2` and `word2` is a prefix of `word1`, the order is wrong.
|
|
|
|
Example: `["apple", "app"]` should return `False` because "apple" is longer and "app" is its prefix.
|
|
wrong_approach: "Only comparing characters without checking lengths"
|
|
correct_approach: "After the loop, check if word1 is longer than word2"
|
|
|
|
- title: Comparing All Pairs Instead of Adjacent Pairs
|
|
description: |
|
|
Some solutions try to compare every word with every other word, resulting in O(n²) comparisons where n is the number of words.
|
|
|
|
This is unnecessary! Due to the transitive property of ordering (if a ≤ b and b ≤ c, then a ≤ c), you only need to check adjacent pairs. If `words[0] ≤ words[1]` and `words[1] ≤ words[2]`, then `words[0] ≤ words[2]` is guaranteed.
|
|
wrong_approach: "Nested loops comparing all word pairs"
|
|
correct_approach: "Single pass comparing consecutive pairs"
|
|
|
|
- title: Using Character ASCII Values
|
|
description: |
|
|
Don't compare characters using their built-in ASCII values (`ord(c)`). The alien alphabet has a custom order that may differ completely from ASCII.
|
|
|
|
For example, in `order = "hlabcdefgijkmnopqrstuvwxyz"`, the character `h` has a lower priority than `l`, even though `ord('h') > ord('l')` is `False` in ASCII.
|
|
|
|
Always use the custom order map for comparisons.
|
|
wrong_approach: "Using ord() or direct character comparison"
|
|
correct_approach: "Using order_map[char] for priority lookups"
|
|
|
|
key_takeaways:
|
|
- "**Adjacent pair checking**: To verify a list is sorted, you only need to check consecutive pairs — the transitive property handles the rest"
|
|
- "**Hash map for custom ordering**: When dealing with non-standard orderings, build a priority map for O(1) lookups"
|
|
- "**Prefix rule in lexicographic order**: The shorter word comes first when one is a prefix of the other"
|
|
- "**Foundation for sorting problems**: This comparison logic is the building block for implementing custom sort comparators"
|
|
|
|
time_complexity: "O(m) where m is the total number of characters across all words. We examine each character at most once during pairwise comparisons."
|
|
space_complexity: "O(1). The order map has exactly 26 entries (fixed size), regardless of input size."
|
|
|
|
solutions:
|
|
- approach_name: Hash Map with Adjacent Comparison
|
|
is_optimal: true
|
|
code: |
|
|
def is_alien_sorted(words: list[str], order: str) -> bool:
|
|
# Build a map from character to its priority (position in alien alphabet)
|
|
order_map = {char: i for i, char in enumerate(order)}
|
|
|
|
def is_sorted_pair(word1: str, word2: str) -> bool:
|
|
"""Check if word1 comes before or equals word2 in alien order."""
|
|
# Compare character by character
|
|
for c1, c2 in zip(word1, word2):
|
|
if order_map[c1] < order_map[c2]:
|
|
# word1 is definitely smaller
|
|
return True
|
|
elif order_map[c1] > order_map[c2]:
|
|
# word1 is definitely larger — out of order!
|
|
return False
|
|
# Characters are equal, continue to next position
|
|
|
|
# All compared characters matched
|
|
# word1 must not be longer than word2 (prefix rule)
|
|
return len(word1) <= len(word2)
|
|
|
|
# Check all adjacent pairs
|
|
for i in range(len(words) - 1):
|
|
if not is_sorted_pair(words[i], words[i + 1]):
|
|
return False
|
|
|
|
return True
|
|
explanation: |
|
|
**Time Complexity:** O(m) where m is the total number of characters in all words. Each character is compared at most once.
|
|
|
|
**Space Complexity:** O(1) — The order map always contains exactly 26 entries.
|
|
|
|
We build a priority map for O(1) character lookups, then verify each adjacent pair follows the alien lexicographic order. The key insight is that checking adjacent pairs is sufficient due to transitivity.
|
|
|
|
- approach_name: Inline Comparison (No Helper Function)
|
|
is_optimal: true
|
|
code: |
|
|
def is_alien_sorted(words: list[str], order: str) -> bool:
|
|
# Map each character to its position in the alien alphabet
|
|
order_map = {c: i for i, c in enumerate(order)}
|
|
|
|
for i in range(len(words) - 1):
|
|
word1, word2 = words[i], words[i + 1]
|
|
|
|
# Compare characters at each position
|
|
for j in range(min(len(word1), len(word2))):
|
|
if order_map[word1[j]] < order_map[word2[j]]:
|
|
# word1 < word2, this pair is correctly ordered
|
|
break
|
|
elif order_map[word1[j]] > order_map[word2[j]]:
|
|
# word1 > word2, out of order!
|
|
return False
|
|
# Characters equal, continue checking
|
|
else:
|
|
# Exhausted comparison — check prefix rule
|
|
if len(word1) > len(word2):
|
|
return False
|
|
|
|
return True
|
|
explanation: |
|
|
**Time Complexity:** O(m) where m is the total number of characters.
|
|
|
|
**Space Complexity:** O(1) — Fixed-size order map.
|
|
|
|
This version inlines the comparison logic and uses Python's for-else construct. The `else` block executes only if the loop completes without `break`, meaning all compared characters were equal — at which point we apply the prefix rule.
|