1556. Thousand Separator
Problem Description
Given an integer n
, you need to format it by adding dots (".") as thousands separators and return the formatted number as a string.
The problem asks you to take any non-negative integer and insert dots every three digits from the right to make the number more readable. For example:
- Input:
987
→ Output:"987"
(no separator needed) - Input:
1234
→ Output:"1.234"
- Input:
123456789
→ Output:"123.456.789"
- Input:
0
→ Output:"0"
The solution works by extracting digits from right to left using division and modulo operations. It keeps a counter to track when to insert a dot (every 3 digits). The digits and dots are collected in a list, then reversed and joined to form the final string since we process the number from right to left but need to display it from left to right.
The key steps in the algorithm are:
- Extract the rightmost digit using
divmod(n, 10)
- Add the digit to the result list
- Increment a counter
- If we've processed 3 digits and there are more digits to process, add a dot
- Continue until all digits are processed
- Reverse the result list and join into a string
Intuition
When we need to add thousands separators to a number, the natural way to think about it is from right to left - we place a dot after every group of 3 digits starting from the rightmost position. This mirrors how we naturally read large numbers: we group digits in threes from the right.
The challenge is that we need to process the number from right to left (to know where to place dots), but strings are typically built from left to right. This suggests we should extract digits from the right side of the number.
To extract digits from right to left, we can repeatedly use the modulo operation n % 10
to get the rightmost digit, and integer division n // 10
to remove that digit from the number. This is a standard technique for digit extraction.
As we extract each digit, we need to track how many consecutive digits we've processed without adding a separator. Once we hit 3 digits, we insert a dot and reset our counter. The key insight is that we only add a dot if there are more digits to process (when n > 0
after division), preventing unnecessary dots at the beginning of the number.
Since we're building the result from right to left but need to display it from left to right, we collect everything in a list and reverse it at the end. This is more efficient than string concatenation and gives us the correct left-to-right ordering.
The divmod(n, 10)
function elegantly combines both operations we need - getting the quotient (remaining number) and remainder (current digit) in one step, making the code cleaner and slightly more efficient.
Solution Approach
The implementation uses a digit extraction approach with a counter to track when to insert separators.
We initialize two variables:
cnt = 0
: A counter to track how many digits we've processed in the current groupans = []
: A list to store the digits and dots as we build the result
The main loop continues indefinitely with while 1:
and processes the number digit by digit:
-
Extract digit: Use
divmod(n, 10)
to simultaneously get:n
: The quotient (remaining number after removing the rightmost digit)v
: The remainder (the rightmost digit)
-
Store digit: Convert the digit
v
to string and append toans
list -
Update counter: Increment
cnt
to track we've processed one more digit -
Check termination: If
n == 0
, we've processed all digits and break from the loop -
Insert separator: If
cnt == 3
(we've collected 3 digits), we:- Append a dot
'.'
to the list - Reset
cnt = 0
to start counting the next group
- Append a dot
The crucial part is that we check for separator insertion after checking if n == 0
. This ensures we don't add an unnecessary dot at the beginning of the number (which would appear at the end before reversal).
Finally, since we built the result from right to left, we reverse the list with ans[::-1]
and join all elements into a string with ''.join()
.
For example, with input 1234
:
- First iteration: Extract
4
,ans = ['4']
,cnt = 1
- Second iteration: Extract
3
,ans = ['4', '3']
,cnt = 2
- Third iteration: Extract
2
,ans = ['4', '3', '2']
,cnt = 3
, add dot:ans = ['4', '3', '2', '.']
, resetcnt = 0
- Fourth iteration: Extract
1
,ans = ['4', '3', '2', '.', '1']
,cnt = 1
n = 0
, break- Reverse and join:
'1.234'
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the algorithm with the input n = 12345
:
Initial State:
n = 12345
cnt = 0
(digit counter)ans = []
(result list)
Iteration 1:
divmod(12345, 10)
→n = 1234
,v = 5
- Add '5' to ans:
ans = ['5']
- Increment counter:
cnt = 1
- Check if done:
n = 1234
(not zero, continue) - Check separator:
cnt = 1
(not 3, no separator needed)
Iteration 2:
divmod(1234, 10)
→n = 123
,v = 4
- Add '4' to ans:
ans = ['5', '4']
- Increment counter:
cnt = 2
- Check if done:
n = 123
(not zero, continue) - Check separator:
cnt = 2
(not 3, no separator needed)
Iteration 3:
divmod(123, 10)
→n = 12
,v = 3
- Add '3' to ans:
ans = ['5', '4', '3']
- Increment counter:
cnt = 3
- Check if done:
n = 12
(not zero, continue) - Check separator:
cnt = 3
(equals 3, add separator!) - Add dot and reset:
ans = ['5', '4', '3', '.']
,cnt = 0
Iteration 4:
divmod(12, 10)
→n = 1
,v = 2
- Add '2' to ans:
ans = ['5', '4', '3', '.', '2']
- Increment counter:
cnt = 1
- Check if done:
n = 1
(not zero, continue) - Check separator:
cnt = 1
(not 3, no separator needed)
Iteration 5:
divmod(1, 10)
→n = 0
,v = 1
- Add '1' to ans:
ans = ['5', '4', '3', '.', '2', '1']
- Increment counter:
cnt = 2
- Check if done:
n = 0
(zero, exit loop!)
Final Step:
- Reverse the list:
['1', '2', '.', '3', '4', '5']
- Join into string:
"12.345"
The key insight is that we process digits from right to left (5→4→3→2→1), inserting a dot after every third digit. By reversing at the end, we get the correct left-to-right representation with dots properly placed as thousands separators.
Solution Implementation
1class Solution:
2 def thousandSeparator(self, n: int) -> str:
3 """
4 Add thousand separators (dots) to an integer.
5
6 Args:
7 n: A non-negative integer to format with thousand separators
8
9 Returns:
10 A string representation of the number with dots as thousand separators
11 """
12 # Counter to track digits processed in current group (groups of 3)
13 digit_count = 0
14 # List to build the result string from right to left
15 result = []
16
17 # Process digits from right to left
18 while True:
19 # Extract the rightmost digit using divmod
20 n, digit = divmod(n, 10)
21
22 # Add the current digit to result
23 result.append(str(digit))
24 digit_count += 1
25
26 # If no more digits left, exit the loop
27 if n == 0:
28 break
29
30 # After every 3 digits, add a thousand separator
31 if digit_count == 3:
32 result.append('.')
33 digit_count = 0 # Reset counter for next group
34
35 # Reverse the result since we built it backwards
36 return ''.join(result[::-1])
37
1class Solution {
2 /**
3 * Adds thousand separators (dots) to an integer number.
4 * For example: 1234567 becomes "1.234.567"
5 *
6 * @param n The integer to format with thousand separators
7 * @return A string representation of the number with dots as thousand separators
8 */
9 public String thousandSeparator(int n) {
10 // Counter to track digits processed in current group (groups of 3)
11 int digitCount = 0;
12
13 // StringBuilder to construct the result string efficiently
14 StringBuilder result = new StringBuilder();
15
16 // Process digits from right to left
17 while (true) {
18 // Extract the rightmost digit
19 int currentDigit = n % 10;
20
21 // Remove the rightmost digit from n
22 n /= 10;
23
24 // Append the current digit to result (building in reverse)
25 result.append(currentDigit);
26
27 // Increment the digit counter for current group
28 ++digitCount;
29
30 // Check if all digits have been processed
31 if (n == 0) {
32 break;
33 }
34
35 // Add separator after every 3 digits
36 if (digitCount == 3) {
37 result.append('.');
38 digitCount = 0; // Reset counter for next group
39 }
40 }
41
42 // Reverse the string since we built it backwards
43 return result.reverse().toString();
44 }
45}
46
1class Solution {
2public:
3 string thousandSeparator(int n) {
4 // Counter to track digits processed (for inserting separator every 3 digits)
5 int digitCount = 0;
6
7 // Result string to build the formatted number
8 string result;
9
10 // Process digits from right to left
11 while (true) {
12 // Extract the rightmost digit
13 int digit = n % 10;
14 n /= 10;
15
16 // Append the digit to result (building string in reverse)
17 result += to_string(digit);
18
19 // If all digits are processed, exit the loop
20 if (n == 0) {
21 break;
22 }
23
24 // Increment digit counter and insert separator after every 3 digits
25 digitCount++;
26 if (digitCount == 3) {
27 result += '.';
28 digitCount = 0; // Reset counter for next group
29 }
30 }
31
32 // Reverse the string to get the correct order (since we built it backwards)
33 reverse(result.begin(), result.end());
34
35 return result;
36 }
37};
38
1function thousandSeparator(n: number): string {
2 // Counter to track digits processed (for inserting separator every 3 digits)
3 let digitCount: number = 0;
4
5 // Result string to build the formatted number
6 let result: string = "";
7
8 // Process digits from right to left
9 while (true) {
10 // Extract the rightmost digit
11 const digit: number = n % 10;
12 n = Math.floor(n / 10);
13
14 // Append the digit to result (building string in reverse)
15 result += digit.toString();
16
17 // If all digits are processed, exit the loop
18 if (n === 0) {
19 break;
20 }
21
22 // Increment digit counter and insert separator after every 3 digits
23 digitCount++;
24 if (digitCount === 3) {
25 result += '.';
26 digitCount = 0; // Reset counter for next group
27 }
28 }
29
30 // Reverse the string to get the correct order (since we built it backwards)
31 return result.split('').reverse().join('');
32}
33
Time and Space Complexity
Time Complexity: O(d)
where d
is the number of digits in the integer n
.
The algorithm processes each digit of the number exactly once. In each iteration of the while loop:
divmod(n, 10)
operation takesO(1)
time- Appending to the list takes
O(1)
amortized time - String conversion and other operations are
O(1)
Since the number of digits in n
is ⌊log₁₀(n)⌋ + 1
for n > 0
(or 1 for n = 0
), the loop runs d
times. The final ans[::-1]
reversal takes O(d)
time, and ''.join()
also takes O(d)
time. Therefore, the overall time complexity is O(d) + O(d) + O(d) = O(d)
.
Space Complexity: O(d)
where d
is the number of digits in the integer n
.
The space is used for:
- The
ans
list which stores all digits plus separators. In the worst case, this contains approximatelyd + ⌊(d-1)/3⌋
elements (digits plus dots) - The reversed list created by
ans[::-1]
which has the same size - The final string created by
''.join()
which has the same size
All these are proportional to the number of digits d
, resulting in a space complexity of O(d)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Adding Extra Separator at the Beginning
One of the most common mistakes is placing the separator check before the termination condition, which results in an extra dot at the beginning of numbers with digits that are multiples of 3.
Incorrect Implementation:
def thousandSeparator(self, n: int) -> str:
digit_count = 0
result = []
while True:
n, digit = divmod(n, 10)
result.append(str(digit))
digit_count += 1
# Wrong: Checking separator BEFORE termination
if digit_count == 3:
result.append('.')
digit_count = 0
if n == 0:
break
return ''.join(result[::-1])
Problem: For input 123
, this produces ".123"
instead of "123"
because we add a dot after processing the third digit even though there are no more digits left.
Solution: Always check if there are more digits to process (n == 0
) before adding a separator. The correct order is:
- Process digit
- Check if we're done
- Only then add separator if needed
2. Handling Edge Case of Zero
Another pitfall is not properly handling when the input is 0
. Some implementations might return an empty string or fail to process zero correctly.
Incorrect Approach:
def thousandSeparator(self, n: int) -> str:
if n == 0:
return "" # Wrong: should return "0"
# ... rest of the code
Solution: The algorithm should naturally handle 0
without special casing. When n = 0
, the first iteration extracts the digit 0
, adds it to the result, then breaks since n
becomes 0
.
3. Using String Concatenation Instead of List
Building the result using string concatenation in a loop is inefficient due to string immutability in Python.
Inefficient Implementation:
def thousandSeparator(self, n: int) -> str:
result = "" # Using string instead of list
digit_count = 0
while True:
n, digit = divmod(n, 10)
result = str(digit) + result # String concatenation
digit_count += 1
if n == 0:
break
if digit_count == 3:
result = "." + result # More string concatenation
digit_count = 0
return result
Problem: Each string concatenation creates a new string object, leading to O(n²) time complexity where n is the number of digits.
Solution: Use a list to collect characters and join them at the end. This maintains O(n) time complexity.
4. Incorrect Counter Reset Logic
Forgetting to reset the counter after adding a separator or resetting it at the wrong time.
Incorrect:
if digit_count == 3: result.append('.') # Forgot to reset: digit_count = 0
This would cause dots to be placed incorrectly after the first group of three digits.
Solution: Always reset the counter immediately after adding a separator to start counting the next group of three digits.
Which data structure is used to implement priority queue?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!