title: Excel Sheet Column Title
slug: excel-sheet-column-title
difficulty: easy
leetcode_id: 168
leetcode_url: https://leetcode.com/problems/excel-sheet-column-title/
categories:
  - strings
  - math
patterns:
  - slug: greedy
    is_optimal: true

function_signature: "def convert_to_title(column_number: int) -> str:"

test_cases:
  visible:
    - input: { column_number: 1 }
      expected: "A"
    - input: { column_number: 28 }
      expected: "AB"
    - input: { column_number: 701 }
      expected: "ZY"
  hidden:
    - input: { column_number: 26 }
      expected: "Z"
    - input: { column_number: 27 }
      expected: "AA"
    - input: { column_number: 52 }
      expected: "AZ"

description: |
  Given an integer `columnNumber`, return *its corresponding column title as it appears in an Excel sheet*.

  For example:

  ```
  A -> 1
  B -> 2
  C -> 3
  ...
  Z -> 26
  AA -> 27
  AB -> 28
  ...
  ```

constraints: |
  - `1 <= columnNumber <= 2^31 - 1`

examples:
  - input: "columnNumber = 1"
    output: '"A"'
    explanation: "The 1st column in Excel is labeled 'A'."
  - input: "columnNumber = 28"
    output: '"AB"'
    explanation: "After Z (26), we wrap to AA (27), then AB (28)."
  - input: "columnNumber = 701"
    output: '"ZY"'
    explanation: "701 = 26 * 26 + 25 = 676 + 25. The first character is Z (26th letter), and the second is Y (25th letter)."

explanation:
  intuition: |
    This problem is essentially converting a number to **base-26**, but with an important twist: Excel columns are **1-indexed**, not 0-indexed.

    In standard base-26 (like hexadecimal is base-16), the digits would be 0-25. But Excel uses A-Z representing 1-26. There's no "zero" character!

    Think of it like this: imagine you're reading an odometer, but instead of digits 0-9, you have letters A-Z. And unlike a normal odometer where 0 exists, this one starts at A (which represents 1).

    The key insight is that before extracting each "digit", we need to **subtract 1** to shift from 1-indexed to 0-indexed. This converts our 1-26 range to 0-25, which maps perfectly to A-Z using modulo arithmetic.

    For example, with `columnNumber = 28`:
    - Subtract 1 → 27
    - 27 % 26 = 1 → maps to 'B'
    - 27 // 26 = 1
    - Subtract 1 → 0
    - 0 % 26 = 0 → maps to 'A'
    - Result: "AB" (reversed from our extraction order)

  approach: |
    We solve this using **repeated division with 1-index adjustment**:

    **Step 1: Initialise result string**

    - `result`: Empty string to build our column title

    &nbsp;

    **Step 2: Extract characters from right to left**

    - While `columnNumber > 0`:
      - **Subtract 1** from `columnNumber` to convert from 1-indexed to 0-indexed
      - Get the remainder when divided by 26 — this gives us the current character (0 = A, 1 = B, ..., 25 = Z)
      - Convert the remainder to a character using `chr(remainder + ord('A'))`
      - **Prepend** this character to the result (or append and reverse at the end)
      - Divide `columnNumber` by 26 to move to the next position

    &nbsp;

    **Step 3: Return the result**

    - Return the constructed column title string

    &nbsp;

    The subtraction step is crucial — it handles the fact that there's no "zero" in Excel's numbering. Without it, 'Z' (26) would incorrectly produce 'AZ' instead of just 'Z'.

  common_pitfalls:
    - title: Forgetting the 1-Index Adjustment
      description: |
        The most common mistake is treating this as standard base-26 conversion without accounting for 1-indexing.

        For example, without subtracting 1:
        - `columnNumber = 26` → 26 % 26 = 0, 26 // 26 = 1 → gives "A" + something
        - But the answer should be just "Z"!

        The subtraction converts our 1-26 range to 0-25, making the modulo operation work correctly.
      wrong_approach: "Direct modulo without adjustment"
      correct_approach: "Subtract 1 before each modulo operation"

    - title: Building String in Wrong Order
      description: |
        When extracting digits via repeated division, we get them in reverse order (rightmost first). A common error is appending characters and forgetting to reverse, or getting confused about which end to add to.

        For `28`:
        - First extraction gives 'B' (the rightmost character)
        - Second extraction gives 'A' (the leftmost character)
        - If we append: "BA" (wrong!)
        - If we prepend: "AB" (correct!)
      wrong_approach: "Appending characters without reversing"
      correct_approach: "Prepend each character, or append then reverse at the end"

    - title: Off-By-One with Character Mapping
      description: |
        After the modulo operation, the remainder is 0-25. Some implementations incorrectly use `chr(remainder + ord('A') + 1)` or similar, resulting in off-by-one errors.

        Remember: remainder 0 should map to 'A', remainder 1 to 'B', etc. Since `ord('A')` is 65, we use `chr(remainder + 65)` or `chr(remainder + ord('A'))`.

  key_takeaways:
    - "**1-indexed systems require adjustment**: When a numbering system starts at 1 instead of 0, subtract 1 before modulo operations"
    - "**Base conversion pattern**: This technique of repeated division and modulo extends to any base conversion (binary, hex, etc.)"
    - "**Build strings carefully**: When extracting digits right-to-left, remember to reverse or prepend"
    - "**Related problems**: Excel Sheet Column Number (LC 171) is the inverse operation — converting title back to number"

  time_complexity: "O(log<sub>26</sub> n). Each iteration divides the number by 26, so we perform approximately log base 26 of n iterations."
  space_complexity: "O(log<sub>26</sub> n). The output string length is proportional to the number of iterations."

solutions:
  - approach_name: Iterative Division
    is_optimal: true
    code: |
      def convert_to_title(column_number: int) -> str:
          result = []

          while column_number > 0:
              # Subtract 1 to convert from 1-indexed to 0-indexed
              column_number -= 1

              # Get the current character (0 = A, 1 = B, ..., 25 = Z)
              remainder = column_number % 26
              result.append(chr(remainder + ord('A')))

              # Move to the next position
              column_number //= 26

          # We built the string right-to-left, so reverse it
          return ''.join(reversed(result))
    explanation: |
      **Time Complexity:** O(log<sub>26</sub> n) — We divide by 26 each iteration.

      **Space Complexity:** O(log<sub>26</sub> n) — The result list stores one character per iteration.

      The key insight is subtracting 1 before each modulo operation. This converts from Excel's 1-indexed system (A=1, Z=26) to a 0-indexed system (A=0, Z=25) that works naturally with modulo arithmetic.

  - approach_name: Recursive Solution
    is_optimal: false
    code: |
      def convert_to_title(column_number: int) -> str:
          if column_number == 0:
              return ""

          # Subtract 1 for 1-indexed adjustment
          column_number -= 1

          # Recursively get the prefix, then append current character
          return convert_to_title(column_number // 26) + chr(column_number % 26 + ord('A'))
    explanation: |
      **Time Complexity:** O(log<sub>26</sub> n) — Same number of operations as iterative.

      **Space Complexity:** O(log<sub>26</sub> n) — Call stack depth plus result string.

      This recursive approach builds the string naturally in the correct order by recursing first (handling higher-order digits) then appending the current character. While elegant, it uses additional stack space compared to the iterative solution.
