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:
  - two-pointers

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.

    &nbsp;

    **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`

    &nbsp;

    **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`

    &nbsp;

    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.
