LeetCode 273 - Integer to English Words
The problem asks us to convert a non-negative integer into its English words representation. Instead of returning digits such as 12345, we must produce a properly formatted English phrase such as "Twelve Thousand Three Hundred Forty Five".
Difficulty: 🔴 Hard
Topics: Math, String, Recursion
Solution
Problem Understanding
The problem asks us to convert a non-negative integer into its English words representation. Instead of returning digits such as 12345, we must produce a properly formatted English phrase such as "Twelve Thousand Three Hundred Forty Five".
The input is a single integer num where:
0 <= num <= 2^31 - 1- The maximum possible value is
2147483647
The output must follow standard English naming conventions for numbers. This includes handling:
- Units such as
One,Two,Three - Tens such as
Twenty,Thirty,Forty - Special numbers from
10to19 - Scale words such as
Thousand,Million, andBillion
The most important observation is that English numbers are naturally grouped into chunks of three digits. For example:
1,234,5671million234thousand567
Each three-digit group can be translated independently and then combined with the correct scale word.
The constraints are small from a computational perspective because even the largest possible integer contains at most ten digits. This means performance is not about handling massive input sizes, but rather about implementing the conversion logic correctly and cleanly.
Several edge cases can easily cause bugs in naive implementations:
0must return"Zero"directly- Numbers between
10and19use unique English words - Tens like
20,30,40must not include trailing unit words - Exact hundreds such as
100should not produce extra spaces - Groups containing zeros, such as
1000010, should skip empty sections cleanly - The largest possible value must still be formatted correctly
A correct solution must carefully handle spacing, scale words, and special naming rules.
Approaches
Brute Force Approach
A brute-force approach would attempt to hardcode or directly simulate every possible number pattern using many conditional branches. For example, we could separately handle:
- One-digit numbers
- Two-digit numbers
- Three-digit numbers
- Thousands
- Millions
- Billions
This approach would repeatedly decompose the number and manually concatenate words for each special case.
While this method can eventually produce the correct answer, it quickly becomes difficult to maintain and error-prone. English number formatting contains many exceptions, especially for numbers between 10 and 19, and handling all combinations manually leads to deeply nested conditionals.
The brute-force method also duplicates logic because the same conversion rules apply repeatedly to every three-digit block.
Key Insight for the Optimal Solution
The key observation is that English numbers follow a recursive structure based on groups of three digits.
Every number can be decomposed into chunks such as:
- Billions
- Millions
- Thousands
- Hundreds
Each chunk contains at most three digits, and every three-digit number follows the exact same conversion rules.
For example:
12345671million234thousand567
If we create a helper function that converts any number from 1 to 999 into English words, we can repeatedly apply it to each chunk and append the appropriate scale word.
This dramatically simplifies the implementation because:
- We solve one small subproblem cleanly
- We reuse the same logic for every chunk
- The recursion naturally mirrors English number structure
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(d) | O(d) | Large amount of repetitive conditional logic |
| Optimal | O(d) | O(d) | Processes three-digit groups recursively and cleanly |
Here, d represents the number of digits in the number. Since the input size is bounded by a 32-bit integer, d is at most 10.
Algorithm Walkthrough
- First, handle the special case where
num == 0. English representation for zero is unique and does not follow the normal grouping rules, so we immediately return"Zero". - Create lookup arrays for:
- Numbers from
1to19 - Multiples of ten from
20to90 - Scale words such as
Thousand,Million, andBillion
These arrays allow constant-time lookup of English words.
3. Define a helper function that converts numbers from 1 to 999.
4. Inside the helper:
-
If the number is less than
20, directly return the corresponding word because these values have unique names. -
If the number is less than
100, compute: -
Tens digit
-
Remaining unit digit
Then combine the tens word with the recursive result of the remainder.
-
Otherwise:
-
Extract the hundreds digit
-
Append
"Hundred" -
Recursively process the remaining two digits
- Process the original number in chunks of three digits using repeated division and modulo operations:
num % 1000extracts the current chunknum //= 1000moves to the next chunk
- For every non-zero chunk:
- Convert it using the helper function
- Append the correct scale word such as
"Thousand"or"Million"
- Build the final result from left to right by prepending newly processed chunks.
- Trim extra whitespace and return the final string.
Why it works
English number naming is hierarchical and based on groups of three digits. Every chunk between 0 and 999 can be translated independently using the same rules. By correctly converting each chunk and attaching the appropriate scale word, the algorithm reconstructs the full English representation exactly as standard English grammar defines it.
Python Solution
class Solution:
def numberToWords(self, num: int) -> str:
if num == 0:
return "Zero"
below_twenty = [
"", "One", "Two", "Three", "Four", "Five",
"Six", "Seven", "Eight", "Nine", "Ten",
"Eleven", "Twelve", "Thirteen", "Fourteen",
"Fifteen", "Sixteen", "Seventeen", "Eighteen",
"Nineteen"
]
tens = [
"", "", "Twenty", "Thirty", "Forty",
"Fifty", "Sixty", "Seventy", "Eighty", "Ninety"
]
thousands = ["", "Thousand", "Million", "Billion"]
def helper(n: int) -> str:
if n == 0:
return ""
if n < 20:
return below_twenty[n]
if n < 100:
return (
tens[n // 10] +
(" " + helper(n % 10) if n % 10 != 0 else "")
)
return (
below_twenty[n // 100] +
" Hundred" +
(" " + helper(n % 100) if n % 100 != 0 else "")
)
result = []
index = 0
while num > 0:
current_chunk = num % 1000
if current_chunk != 0:
chunk_words = helper(current_chunk)
if thousands[index]:
chunk_words += " " + thousands[index]
result.append(chunk_words)
num //= 1000
index += 1
return " ".join(reversed(result))
The implementation begins with a direct check for zero because "Zero" is a special standalone case.
The arrays below_twenty, tens, and thousands serve as lookup tables for the English words. This avoids repeated conditionals and keeps the code clean.
The helper function is responsible for converting numbers from 1 to 999.
For numbers below 20, the function directly returns the corresponding word because these values have irregular English names.
For numbers below 100, the function:
- Finds the tens digit
- Converts the remaining unit digit recursively
For numbers greater than or equal to 100, the function:
- Extracts the hundreds digit
- Appends
"Hundred" - Recursively converts the remaining portion
The main loop repeatedly extracts three-digit chunks using % 1000. Each chunk is translated independently and paired with the correct scale word from the thousands array.
Finally, the chunks are reversed because processing starts from the least significant digits.
Go Solution
package main
import "strings"
func numberToWords(num int) string {
if num == 0 {
return "Zero"
}
belowTwenty := []string{
"", "One", "Two", "Three", "Four", "Five",
"Six", "Seven", "Eight", "Nine", "Ten",
"Eleven", "Twelve", "Thirteen", "Fourteen",
"Fifteen", "Sixteen", "Seventeen", "Eighteen",
"Nineteen",
}
tens := []string{
"", "", "Twenty", "Thirty", "Forty",
"Fifty", "Sixty", "Seventy", "Eighty", "Ninety",
}
thousands := []string{
"", "Thousand", "Million", "Billion",
}
var helper func(int) string
helper = func(n int) string {
if n == 0 {
return ""
}
if n < 20 {
return belowTwenty[n]
}
if n < 100 {
if n%10 == 0 {
return tens[n/10]
}
return tens[n/10] + " " + helper(n%10)
}
if n%100 == 0 {
return belowTwenty[n/100] + " Hundred"
}
return belowTwenty[n/100] + " Hundred " + helper(n%100)
}
result := []string{}
index := 0
for num > 0 {
currentChunk := num % 1000
if currentChunk != 0 {
chunkWords := helper(currentChunk)
if thousands[index] != "" {
chunkWords += " " + thousands[index]
}
result = append([]string{chunkWords}, result...)
}
num /= 1000
index++
}
return strings.Join(result, " ")
}
The Go implementation follows the same algorithmic structure as the Python version. The primary difference is that Go requires explicit slice handling and string concatenation.
Go does not support nested named functions as naturally as Python, so the helper function is declared using a function variable.
Another notable difference is how the result is constructed. Since Go slices do not provide a convenient reverse iterator, chunks are prepended directly into the result slice.
Worked Examples
Example 1
Input:
123
Processing steps:
| Step | Value | Action |
|---|---|---|
| 1 | 123 % 1000 = 123 | Extract chunk |
| 2 | helper(123) | Convert chunk |
| 3 | 123 // 100 = 1 | "One Hundred" |
| 4 | Remaining = 23 | Convert recursively |
| 5 | 23 // 10 = 2 | "Twenty" |
| 6 | Remaining = 3 | "Three" |
| 7 | Combine | "One Hundred Twenty Three" |
Final output:
One Hundred Twenty Three
Example 2
Input:
12345
Chunk decomposition:
| Chunk | Scale |
|---|---|
| 345 | "" |
| 12 | Thousand |
Processing:
| Step | Chunk | Result |
|---|---|---|
| 1 | 345 | "Three Hundred Forty Five" |
| 2 | 12 | "Twelve Thousand" |
| 3 | Combine | "Twelve Thousand Three Hundred Forty Five" |
Final output:
Twelve Thousand Three Hundred Forty Five
Example 3
Input:
1234567
Chunk decomposition:
| Chunk | Scale |
|---|---|
| 567 | "" |
| 234 | Thousand |
| 1 | Million |
Processing:
| Step | Chunk | Words |
|---|---|---|
| 1 | 567 | "Five Hundred Sixty Seven" |
| 2 | 234 | "Two Hundred Thirty Four Thousand" |
| 3 | 1 | "One Million" |
| 4 | Combine | "One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven" |
Final output:
One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(d) | Each digit is processed a constant number of times |
| Space | O(d) | Recursive calls and result storage scale with digit count |
The number is processed in groups of three digits, and each group requires constant work. Since a 32-bit integer contains at most 10 digits, the runtime is effectively constant in practice. The recursion depth is also bounded because the helper only processes numbers up to 999.
Test Cases
solution = Solution()
assert solution.numberToWords(0) == "Zero" # special zero case
assert solution.numberToWords(5) == "Five" # single digit
assert solution.numberToWords(13) == "Thirteen" # teen number
assert solution.numberToWords(20) == "Twenty" # exact multiple of ten
assert solution.numberToWords(45) == "Forty Five" # tens + units
assert solution.numberToWords(100) == "One Hundred" # exact hundred
assert solution.numberToWords(123) == "One Hundred Twenty Three" # standard three digits
assert solution.numberToWords(1000) == "One Thousand" # exact thousand
assert solution.numberToWords(1001) == "One Thousand One" # zero in middle
assert solution.numberToWords(1010) == "One Thousand Ten" # skip empty hundreds
assert solution.numberToWords(12345) == (
"Twelve Thousand Three Hundred Forty Five"
) # multiple chunks
assert solution.numberToWords(1000000) == (
"One Million"
) # exact million
assert solution.numberToWords(1000010) == (
"One Million Ten"
) # internal zero chunks
assert solution.numberToWords(1234567) == (
"One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven"
) # large mixed number
assert solution.numberToWords(2147483647) == (
"Two Billion One Hundred Forty Seven Million "
"Four Hundred Eighty Three Thousand "
"Six Hundred Forty Seven"
) # maximum 32-bit integer
| Test | Why |
|---|---|
0 |
Validates special handling for zero |
5 |
Tests simple single-digit conversion |
13 |
Verifies teen word lookup |
20 |
Tests exact tens formatting |
45 |
Ensures proper tens and units combination |
100 |
Verifies exact hundred formatting |
123 |
Standard three-digit case |
1000 |
Exact thousand handling |
1001 |
Ensures zeros between chunks are skipped |
1010 |
Verifies partial chunk handling |
12345 |
Multi-chunk conversion |
1000000 |
Exact million handling |
1000010 |
Internal empty chunk skipping |
1234567 |
Complex multi-scale formatting |
2147483647 |
Maximum valid input |
Edge Cases
Input Equal to Zero
Zero is the only number that does not naturally fit into the chunk-based decomposition approach. If we attempted normal processing, no chunks would be added to the result, producing an empty string instead of "Zero".
The implementation handles this immediately at the beginning by returning "Zero" before any further logic executes.
Numbers Between 10 and 19
English names for numbers between 10 and 19 are irregular. For example:
11is"Eleven"13is"Thirteen"
These cannot be constructed from generic tens and unit rules.
The implementation avoids errors by storing all values from 0 to 19 in a lookup table and returning them directly.
Chunks Containing Zeros
Numbers such as:
1000010
contain empty chunks in the middle. A naive implementation might accidentally generate:
One Million Thousand Ten
which is incorrect.
The algorithm solves this by skipping any chunk whose value is 0. Only non-zero chunks are converted and appended to the final result.
Exact Multiples of Hundred or Thousand
Numbers like:
10010001000000
can accidentally produce trailing spaces or unnecessary words.
The helper function carefully checks whether the remainder is zero before recursively appending additional words. This guarantees clean formatting without extra whitespace or invalid phrases.