Facebook Pixel

953. Verifying an Alien Dictionary

EasyArrayHash TableString
Leetcode Link

Problem Description

You're given words from an alien language that uses the same 26 lowercase English letters (a-z), but in a potentially different alphabetical order. The aliens have their own custom ordering of these letters.

Your task is to determine whether a given list of words is sorted according to the alien dictionary order.

Input:

  • words: A list of strings containing words written in the alien language
  • order: A string of 26 characters representing the alien alphabetical order (a permutation of a-z)

Output:

  • Return true if the words are sorted lexicographically according to the alien order
  • Return false otherwise

Example: If the alien order is "hlabcdefgijkmnopqrstuvwxyz" (where h comes before l, which comes before a, etc.), then:

  • ["hello", "leetcode"] would be sorted correctly because h comes before l in the alien order
  • ["word", "world"] would need to be checked character by character using the alien order

The solution creates a mapping of each character to its position in the alien alphabet, then compares words character by character up to position 20 (assuming maximum word length). For each position i, it checks if all words maintain the correct order at that character position. If at any position the ordering is violated (a previous character has a higher order value than the current one), it returns false. If all characters at a position are different and properly ordered, the words are confirmed to be sorted.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To check if words are sorted in an alien dictionary, we need to compare them the same way we'd compare words in a regular dictionary - character by character from left to right. The key insight is that we need to translate the alien ordering into something we can work with numerically.

Think about how you check if words are sorted in English: you compare the first letters, and if they're the same, you move to the second letters, and so on. The difference here is that instead of using the standard a < b < c... ordering, we have a custom ordering defined by the order string.

The natural approach is to create a mapping where each character gets assigned its position in the alien alphabet. For example, if order = "bac...", then b has position 0, a has position 1, and c has position 2. This way, we can compare characters numerically - a character with a smaller position value comes before one with a larger position value.

The solution iterates through each character position (column-wise) across all words. At position i, it extracts the i-th character from each word and checks if these characters are in non-decreasing order according to the alien alphabet. If we find any position where a word has a character that comes after the corresponding character in the previous word (higher position value), the list isn't sorted.

The clever part is handling words of different lengths - when a word doesn't have a character at position i (it's too short), we treat it as having value -1, which naturally comes before any actual character. This handles cases like "app" coming before "apple" correctly.

The algorithm stops early when it finds a position where all words have different characters that are properly ordered (the valid flag), confirming the sort order without needing to check further positions.

Solution Approach

The solution implements a column-wise comparison strategy to verify the alien dictionary ordering:

Step 1: Create Character-to-Position Mapping

m = {c: i for i, c in enumerate(order)}

This creates a dictionary where each character maps to its position in the alien alphabet. For example, if order = "bac...", then m = {'b': 0, 'a': 1, 'c': 2, ...}.

Step 2: Column-wise Iteration The algorithm iterates through character positions 0 to 19 (assuming maximum word length of 20):

for i in range(20):

Step 3: Character Comparison at Each Position For each position i, the algorithm:

  • Initializes prev = -1 to track the previous word's character value
  • Sets valid = True to check if all characters at this position are distinct and ordered

Step 4: Process Each Word

for x in words:
    curr = -1 if i >= len(x) else m[x[i]]

For each word, it extracts the character at position i:

  • If the word is too short (i >= len(x)), assign -1 as the value
  • Otherwise, get the character's position value from the mapping m[x[i]]

Step 5: Order Validation

if prev > curr:
    return False
if prev == curr:
    valid = False
prev = curr
  • If prev > curr: The previous word's character comes after the current word's character in the alien order, so the list is not sorted - return False
  • If prev == curr: The characters are the same, so we need to check the next position - set valid = False
  • Update prev for the next iteration

Step 6: Early Termination

if valid:
    return True

If valid remains True after checking all words at position i, it means all characters at this position are different and in the correct order. The words are confirmed to be sorted, so return True.

Step 7: Default Return If the loop completes without finding a definitive answer (all positions checked with ties), return True as the words are considered sorted.

This approach efficiently handles edge cases like words of different lengths and ensures lexicographic ordering according to the alien alphabet.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the algorithm with a concrete example:

Input:

  • words = ["cat", "car", "card"]
  • order = "cabdefghijklmnopqrstuvwxyz" (where 'c' comes first, then 'a', then 'b', etc.)

Step 1: Create mapping

m = {'c': 0, 'a': 1, 'b': 2, 'd': 3, ..., 't': 19, 'r': 17, ...}

Step 2: Check position i = 0 (first character)

  • Word "cat": character 'c' → m['c'] = 0
  • Word "car": character 'c' → m['c'] = 0
  • Word "card": character 'c' → m['c'] = 0

Comparison sequence:

  • prev = -1 (initial)
  • "cat": curr = 0, prev (-1) ≤ curr (0) ✓, prev = curr so valid = False
  • "car": curr = 0, prev (0) ≤ curr (0) ✓, prev = curr so valid = False
  • "card": curr = 0, prev (0) ≤ curr (0) ✓, prev = curr so valid = False

All first characters are the same, so valid = False, continue to next position.

Step 3: Check position i = 1 (second character)

  • Word "cat": character 'a' → m['a'] = 1
  • Word "car": character 'a' → m['a'] = 1
  • Word "card": character 'a' → m['a'] = 1

Comparison sequence:

  • prev = -1 (reset)
  • "cat": curr = 1, prev (-1) ≤ curr (1) ✓, prev = curr so valid = False
  • "car": curr = 1, prev (1) ≤ curr (1) ✓, prev = curr so valid = False
  • "card": curr = 1, prev (1) ≤ curr (1) ✓, prev = curr so valid = False

All second characters are the same, continue to next position.

Step 4: Check position i = 2 (third character)

  • Word "cat": character 't' → m['t'] = 19
  • Word "car": character 'r' → m['r'] = 17
  • Word "card": character 'r' → m['r'] = 17

Comparison sequence:

  • prev = -1 (reset)
  • "cat": curr = 19, prev (-1) ≤ curr (19) ✓, update prev = 19
  • "car": curr = 17, prev (19) > curr (17) ✗

Since 19 > 17, the character 't' comes after 'r' in the alien order, but "cat" appears before "car" in our list. This violates the sorting order.

Result: Return False

The words are not sorted according to the alien dictionary because "cat" should come after "car" (since 't' comes after 'r' in the alien alphabet).

Solution Implementation

1class Solution:
2    def isAlienSorted(self, words: List[str], order: str) -> bool:
3        # Create a mapping from each character to its position in the alien alphabet
4        char_to_position = {char: position for position, char in enumerate(order)}
5      
6        # Check each position (column) across all words, up to position 20
7        for position_index in range(20):
8            previous_char_value = -1  # Initialize with -1 (smaller than any valid position)
9            all_chars_different = True  # Track if all characters at this position are different
10          
11            # Check each word at the current position
12            for word in words:
13                # Get character value at current position (-1 if position exceeds word length)
14                if position_index >= len(word):
15                    current_char_value = -1  # Treat end of word as smallest value
16                else:
17                    current_char_value = char_to_position[word[position_index]]
18              
19                # If previous character has larger value, words are not sorted
20                if previous_char_value > current_char_value:
21                    return False
22              
23                # If characters are equal, we need to check next position
24                if previous_char_value == current_char_value:
25                    all_chars_different = False
26              
27                previous_char_value = current_char_value
28          
29            # If all characters at this position are different, words are sorted
30            if all_chars_different:
31                return True
32      
33        # After checking 20 positions, assume words are sorted
34        return True
35
1class Solution {
2    public boolean isAlienSorted(String[] words, String order) {
3        // Create a mapping array to store the position of each character in the alien order
4        // orderMap[char - 'a'] = position of char in the alien alphabet
5        int[] orderMap = new int[26];
6        for (int i = 0; i < 26; i++) {
7            orderMap[order.charAt(i) - 'a'] = i;
8        }
9      
10        // Check each column position across all words (column-by-column comparison)
11        // Maximum 20 iterations is sufficient for typical constraints
12        for (int columnIndex = 0; columnIndex < 20; columnIndex++) {
13            int previousCharOrder = -1;
14            boolean allCharsDifferent = true;
15          
16            // Compare characters at the current column position for all words
17            for (String word : words) {
18                // Get the order value of character at current position
19                // If position exceeds word length, use -1 (treats shorter words as smaller)
20                int currentCharOrder = columnIndex >= word.length() ? 
21                    -1 : orderMap[word.charAt(columnIndex) - 'a'];
22              
23                // If previous character has higher order than current, words are not sorted
24                if (previousCharOrder > currentCharOrder) {
25                    return false;
26                }
27              
28                // Track if all characters in this column are different
29                if (previousCharOrder == currentCharOrder) {
30                    allCharsDifferent = false;
31                }
32              
33                previousCharOrder = currentCharOrder;
34            }
35          
36            // If all characters in this column are different and in order,
37            // the words are sorted (no need to check further columns)
38            if (allCharsDifferent) {
39                break;
40            }
41        }
42      
43        return true;
44    }
45}
46
1class Solution {
2public:
3    bool isAlienSorted(vector<string>& words, string order) {
4        // Create a mapping from alien character to its position in the alien alphabet
5        // charToPosition[c] represents the position of character c in the alien order
6        vector<int> charToPosition(26);
7        for (int i = 0; i < 26; ++i) {
8            charToPosition[order[i] - 'a'] = i;
9        }
10      
11        // Check character by character at each position (up to position 20)
12        // This assumes the maximum word length is 20
13        for (int position = 0; position < 20; ++position) {
14            int prevCharPosition = -1;  // Position of previous word's character in alien order
15            bool allDifferent = true;   // Flag to check if all characters at current position are different
16          
17            // Iterate through all words and compare characters at current position
18            for (const auto& word : words) {
19                // Get the alien order position of current character
20                // If position exceeds word length, use -1 (represents empty character)
21                int currCharPosition = (position >= word.size()) ? -1 : charToPosition[word[position] - 'a'];
22              
23                // If previous character comes after current in alien order, words are not sorted
24                if (prevCharPosition > currCharPosition) {
25                    return false;
26                }
27              
28                // If characters are same, we need to check next position
29                if (prevCharPosition == currCharPosition) {
30                    allDifferent = false;
31                }
32              
33                prevCharPosition = currCharPosition;
34            }
35          
36            // If all characters at this position are different and in order, 
37            // the words are sorted (no need to check further positions)
38            if (allDifferent) {
39                break;
40            }
41        }
42      
43        return true;
44    }
45};
46
1/**
2 * Verifies if words are sorted according to an alien dictionary order
3 * @param words - Array of words to check if they are in lexicographical order
4 * @param order - String representing the alien alphabet order
5 * @returns true if words are sorted according to the alien order, false otherwise
6 */
7function isAlienSorted(words: string[], order: string): boolean {
8    // Create a map to store character positions in the alien alphabet
9    const charToOrderMap = new Map<string, number>();
10  
11    // Map each character to its position in the alien alphabet
12    for (const char of order) {
13        charToOrderMap.set(char, charToOrderMap.size);
14    }
15  
16    const wordCount = words.length;
17  
18    // Compare each adjacent pair of words
19    for (let i = 1; i < wordCount; i++) {
20        const previousWord = words[i - 1];
21        const currentWord = words[i];
22        const minLength = Math.min(previousWord.length, currentWord.length);
23        let foundDifference = false;
24      
25        // Compare characters at each position
26        for (let j = 0; j < minLength; j++) {
27            const previousCharOrder = charToOrderMap.get(previousWord[j])!;
28            const currentCharOrder = charToOrderMap.get(currentWord[j])!;
29          
30            // If previous word's character comes after current word's character, not sorted
31            if (previousCharOrder > currentCharOrder) {
32                return false;
33            }
34          
35            // If we found a character that determines the order, mark and break
36            if (previousCharOrder < currentCharOrder) {
37                foundDifference = true;
38                break;
39            }
40        }
41      
42        // If all compared characters are equal but previous word is longer, not sorted
43        // Example: "apple" should not come before "app"
44        if (!foundDifference && previousWord.length > currentWord.length) {
45            return false;
46        }
47    }
48  
49    return true;
50}
51

Time and Space Complexity

Time Complexity: O(n * m) where n is the total number of characters across all words and m is the length of the alphabet (order string).

  • Creating the mapping dictionary m takes O(m) time where m is the length of the order string (26 for the alphabet).
  • The outer loop runs at most 20 times (constant).
  • The inner loop iterates through all words, which is O(w) where w is the number of words.
  • For each word, we access at most one character and perform a dictionary lookup, which is O(1).
  • In the worst case, we examine all characters across all words before returning, giving us O(n) where n is the total number of characters.
  • Since the outer loop is bounded by a constant (20), the overall time complexity is O(n + m), which simplifies to O(n) when n >> m.

However, a more precise analysis shows that we iterate through positions (up to 20) and for each position check all words, so it's O(min(20, maxLength) * w) where w is the number of words and maxLength is the length of the longest word. This can be simplified to O(n) where n is the total number of characters across all words.

Space Complexity: O(m) where m is the length of the order string.

  • The mapping dictionary m stores each character in the order string with its index, requiring O(m) space.
  • The variables prev, curr, and valid use constant space O(1).
  • No additional data structures are created that scale with input size.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Handling of Words with Different Lengths

A critical pitfall in this solution is the assumption that shorter words should always come before longer words when one is a prefix of the other. The code assigns -1 to positions beyond a word's length, which makes shorter words automatically "smaller" than longer words at those positions.

Problem Example:

  • Alien order: "abcdefghijklmnopqrstuvwxyz" (standard order)
  • Words: ["apple", "app"]
  • Expected: False (because "apple" should come after "app" in standard lexicographic order)
  • Current code returns: True (incorrectly)

Why it fails: When comparing at position 3:

  • "apple" has character 'l' with value 11
  • "app" has no character (assigned -1)
  • Since 11 > -1, the code thinks "apple" > "app", which seems correct
  • But the issue is that the code continues checking and never properly validates that "app" should come first

2. Fixed Position Limit of 20

The hardcoded limit of 20 positions assumes no word will be longer than 20 characters. If words exceed this length and differ only after position 20, the algorithm will incorrectly return True.

Solution:

class Solution:
    def isAlienSorted(self, words: List[str], order: str) -> bool:
        # Create character-to-position mapping
        char_to_position = {char: position for position, char in enumerate(order)}
      
        # Compare adjacent words pairwise
        for i in range(len(words) - 1):
            word1, word2 = words[i], words[i + 1]
          
            # Compare characters one by one
            for j in range(len(word1)):
                # If word2 is shorter than word1 and is a prefix, wrong order
                if j >= len(word2):
                    return False
              
                # Get character values in alien order
                char1_value = char_to_position[word1[j]]
                char2_value = char_to_position[word2[j]]
              
                # If char1 comes after char2 in alien order, wrong order
                if char1_value > char2_value:
                    return False
                # If char1 comes before char2, this pair is valid
                elif char1_value < char2_value:
                    break
                # If equal, continue to next character
          
        return True

Key Improvements:

  1. Pairwise comparison: Instead of column-wise checking, compare adjacent words directly
  2. Proper prefix handling: Explicitly check if a longer word comes before its prefix
  3. No arbitrary length limit: Works for words of any length
  4. Clearer logic: Each word pair is validated completely before moving to the next
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of these properties could exist for a graph but not a tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More