738. Monotone Increasing Digits
Problem Description
You need to find the largest number that has monotone increasing digits and is less than or equal to a given integer n
.
A number has monotone increasing digits when each digit is less than or equal to the digit that comes after it. In other words, for any two adjacent digits x
and y
(where x
comes before y
), the condition x <= y
must be satisfied.
For example:
1234
has monotone increasing digits (1 ≤ 2 ≤ 3 ≤ 4)1232
does NOT have monotone increasing digits (3 > 2 violates the rule)1333
has monotone increasing digits (1 ≤ 3 ≤ 3 ≤ 3)
Given an integer n
, you need to return the largest possible number that:
- Has monotone increasing digits
- Is less than or equal to
n
For instance, if n = 1232
, the answer would be 1229
because:
1229
has monotone increasing digits (1 ≤ 2 ≤ 2 ≤ 9)1229
is the largest such number that doesn't exceed1232
Intuition
The key insight is to scan the number from left to right and find the first position where the monotone increasing property is violated (where a digit is greater than the next digit). Once we find this violation point, we need to adjust the number to maintain the monotone property while maximizing the result.
Consider the number 1232
. When we scan from left to right:
1 ≤ 2
✓2 ≤ 3
✓3 > 2
✗ (violation found!)
At this violation point, we can't keep 3
because it breaks the monotone property. We need to reduce it. But simply changing 3
to 2
would give us 1222
, which is not the largest possible answer.
The clever observation is: when we decrease a digit, all digits to its right can be set to 9
to maximize the result. This works because 9
is the largest digit, and any digit followed by 9
maintains the monotone property.
But there's a subtlety: sometimes we need to decrease multiple digits. For example, with 1332
:
- First violation is at
3 > 2
- If we just decrease the second
3
to2
, we get1322
, but3 > 2
is still a violation - We need to keep decreasing backward:
1332
→1299
The algorithm works by:
- Finding the first violation point
- Decreasing the violating digit and potentially propagating this decrease backward if needed
- Setting all remaining digits to the right to
9
to maximize the result
This greedy approach ensures we get the largest possible number with monotone increasing digits that doesn't exceed n
.
Solution Approach
The implementation converts the number to a string (character array) for easier digit manipulation, then follows these steps:
-
Find the first violation point:
i = 1 while i < len(s) and s[i - 1] <= s[i]: i += 1
We scan from left to right, comparing each digit with the next one. The loop continues as long as the monotone property holds (
s[i-1] <= s[i]
). When we exit this loop,i
points to the position right after the first digit that violates the monotone property. -
Handle the violation (if found): If
i < len(s)
, it means we found a violation. Now we need to:a. Decrease digits backward as needed:
while i and s[i - 1] > s[i]: s[i - 1] = str(int(s[i - 1]) - 1) i -= 1
Starting from the violation point, we decrease the previous digit by 1 and move backward. We continue this process while the current digit is still greater than the next digit. This handles cases like
1332
where multiple digits need to be decreased.b. Fill remaining positions with 9:
i += 1 while i < len(s): s[i] = '9' i += 1
After adjusting the violating digits, we move forward one position (since we went backward in the previous step) and set all remaining digits to
9
. This maximizes the result while maintaining the monotone property. -
Return the result:
return int(''.join(s))
Convert the modified character array back to an integer.
Example walkthrough with n = 1232
:
- Convert to
['1', '2', '3', '2']
- Find violation:
i = 3
(because3 > 2
) - Decrease backward:
['1', '2', '2', '2']
(decrease3
to2
) - Fill with 9s:
['1', '2', '2', '9']
- Result:
1229
The time complexity is O(d)
where d
is the number of digits, and space complexity is O(d)
for storing the string representation.
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 trace through the algorithm with n = 1432
:
Step 1: Convert to character array
s = ['1', '4', '3', '2']
Step 2: Find the first violation point
- Start with
i = 1
- Check
s[0] <= s[1]
:'1' <= '4'
✓, soi = 2
- Check
s[1] <= s[2]
:'4' <= '3'
✗ (violation found!) - Stop with
i = 2
Step 3: Handle the violation
- Since
i < len(s)
, we found a violation at position 2
Step 3a: Decrease digits backward
- Check
s[i-1] > s[i]
:'4' > '3'
is true - Decrease
s[1]
from'4'
to'3'
:s = ['1', '3', '3', '2']
- Move backward:
i = 1
- Check
s[i-1] > s[i]
:'1' > '3'
is false - Stop decreasing
Step 3b: Fill remaining positions with 9
- Move forward:
i = 2
- Set
s[2] = '9'
:s = ['1', '3', '9', '2']
- Move to
i = 3
- Set
s[3] = '9'
:s = ['1', '3', '9', '9']
Step 4: Return result
- Join and convert:
1399
Verification:
1399
has monotone increasing digits:1 ≤ 3 ≤ 9 ≤ 9
✓1399 ≤ 1432
✓- This is indeed the largest such number!
Solution Implementation
1class Solution:
2 def monotoneIncreasingDigits(self, n: int) -> int:
3 """
4 Find the largest number that is less than or equal to n with monotone increasing digits.
5 A number has monotone increasing digits if each digit is >= the previous digit.
6
7 Args:
8 n: A non-negative integer
9
10 Returns:
11 The largest integer <= n with monotone increasing digits
12 """
13 # Convert number to list of digit characters for easier manipulation
14 digits = list(str(n))
15 length = len(digits)
16
17 # Find the first position where monotone property breaks (digit decreases)
18 break_point = 1
19 while break_point < length and digits[break_point - 1] <= digits[break_point]:
20 break_point += 1
21
22 # If we found a break in monotone property
23 if break_point < length:
24 # Backtrack to find the position where we need to decrease the digit
25 # This handles cases where we have repeated digits before the break
26 while break_point > 0 and digits[break_point - 1] > digits[break_point]:
27 digits[break_point - 1] = str(int(digits[break_point - 1]) - 1)
28 break_point -= 1
29
30 # Move forward one position after the decreased digit
31 break_point += 1
32
33 # Set all remaining digits to '9' to get the largest possible number
34 while break_point < length:
35 digits[break_point] = '9'
36 break_point += 1
37
38 # Convert the list of digit characters back to an integer
39 return int(''.join(digits))
40
1class Solution {
2 public int monotoneIncreasingDigits(int n) {
3 // Convert the number to a character array for digit manipulation
4 char[] digits = String.valueOf(n).toCharArray();
5
6 // Find the first position where monotone property breaks (digits[i-1] > digits[i])
7 int breakPoint = 1;
8 while (breakPoint < digits.length && digits[breakPoint - 1] <= digits[breakPoint]) {
9 breakPoint++;
10 }
11
12 // If we found a break point (not monotone increasing)
13 if (breakPoint < digits.length) {
14 // Move backwards to find the position where we need to start decrementing
15 // We decrement digits that are greater than their next digit
16 while (breakPoint > 0 && digits[breakPoint - 1] > digits[breakPoint]) {
17 digits[breakPoint - 1]--;
18 breakPoint--;
19 }
20
21 // Move forward one position to start filling with 9s
22 breakPoint++;
23
24 // Fill all remaining digits with 9 to get the largest possible number
25 while (breakPoint < digits.length) {
26 digits[breakPoint] = '9';
27 breakPoint++;
28 }
29 }
30
31 // Convert the character array back to an integer and return
32 return Integer.parseInt(String.valueOf(digits));
33 }
34}
35
1class Solution {
2public:
3 int monotoneIncreasingDigits(int n) {
4 // Convert the number to string for digit manipulation
5 string numStr = to_string(n);
6
7 // Find the first position where monotone property breaks
8 // (where current digit is less than previous digit)
9 int breakPoint = 1;
10 while (breakPoint < numStr.size() && numStr[breakPoint - 1] <= numStr[breakPoint]) {
11 breakPoint++;
12 }
13
14 // If we found a break in monotone property
15 if (breakPoint < numStr.size()) {
16 // Backtrack to find the position where we need to start decreasing
17 // Keep moving back while previous digit is greater than current
18 while (breakPoint > 0 && numStr[breakPoint - 1] > numStr[breakPoint]) {
19 numStr[breakPoint - 1]--; // Decrease the digit to maintain monotone property
20 breakPoint--;
21 }
22
23 // Move to the next position after the decreased digit
24 breakPoint++;
25
26 // Set all remaining digits to '9' to get the maximum possible value
27 while (breakPoint < numStr.size()) {
28 numStr[breakPoint] = '9';
29 breakPoint++;
30 }
31 }
32
33 // Convert the modified string back to integer and return
34 return stoi(numStr);
35 }
36};
37
1function monotoneIncreasingDigits(n: number): number {
2 // Convert the number to string for digit manipulation
3 let numStr: string = n.toString();
4 let numArray: string[] = numStr.split('');
5
6 // Find the first position where monotone property breaks
7 // (where current digit is less than previous digit)
8 let breakPoint: number = 1;
9 while (breakPoint < numArray.length && numArray[breakPoint - 1] <= numArray[breakPoint]) {
10 breakPoint++;
11 }
12
13 // If we found a break in monotone property
14 if (breakPoint < numArray.length) {
15 // Backtrack to find the position where we need to start decreasing
16 // Keep moving back while previous digit is greater than or equal to current
17 while (breakPoint > 0 && numArray[breakPoint - 1] >= numArray[breakPoint]) {
18 // Decrease the digit to maintain monotone property
19 numArray[breakPoint - 1] = (parseInt(numArray[breakPoint - 1]) - 1).toString();
20 breakPoint--;
21 }
22
23 // Move to the next position after the decreased digit
24 breakPoint++;
25
26 // Set all remaining digits to '9' to get the maximum possible value
27 while (breakPoint < numArray.length) {
28 numArray[breakPoint] = '9';
29 breakPoint++;
30 }
31 }
32
33 // Convert the modified string back to integer and return
34 return parseInt(numArray.join(''));
35}
36
Time and Space Complexity
Time Complexity: O(d)
where d
is the number of digits in n
.
- Converting integer
n
to string takesO(d)
time - The first while loop iterates through the digits once in the worst case:
O(d)
- The second while loop (backtracking) can iterate at most
d
times:O(d)
- The third while loop fills remaining positions with '9', at most
d
iterations:O(d)
- Converting the result back to integer takes
O(d)
time - Overall:
O(d) + O(d) + O(d) + O(d) + O(d) = O(d)
Since d = log₁₀(n)
, the time complexity can also be expressed as O(log n)
.
Space Complexity: O(d)
where d
is the number of digits in n
.
- The list
s
stores all digits ofn
, requiringO(d)
space - The string conversion and join operation create temporary strings of size
O(d)
- No recursive calls or additional data structures are used
- Overall space complexity:
O(d)
orO(log n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Not Handling Repeated Digits Before Violation
The Problem:
When you encounter a violation in the monotone property, simply decreasing the digit immediately before the violation point isn't always sufficient. Consider the number 1332
:
- The violation occurs at index 3 (where
3 > 2
) - If you only decrease the digit at index 2 (changing
3
to2
), you get1322
- But
1322
still violates the monotone property because the first3
is greater than the second2
Why This Happens:
The issue arises when there are repeated digits before the violation point. In 1332
, both middle digits are 3
, and when we decrease one of them, we need to also decrease the previous occurrences of the same digit.
The Solution: The code handles this with a backward loop that continues decreasing digits as long as they're greater than their next digit:
while break_point > 0 and digits[break_point - 1] > digits[break_point]:
digits[break_point - 1] = str(int(digits[break_point - 1]) - 1)
break_point -= 1
This ensures that for 1332
:
- First iteration:
1332
→1322
(decrease second3
to2
) - Second iteration:
1322
→1222
(decrease first3
to2
) - Now the monotone property is satisfied
Pitfall 2: Incorrect Index Management After Backtracking
The Problem:
After the backward loop that decreases digits, the break_point
variable has moved backward. If you forget to move it forward again before filling with 9
s, you'll overwrite the wrong digits.
Why This Happens:
The backward loop moves break_point
to handle cascading decrements. After this loop, break_point
points to the position before the last decreased digit. To correctly fill the remaining positions with 9
s, you need to start from the position after the last decreased digit.
The Solution:
Always increment break_point
after the backward loop:
break_point += 1 # Move to the position after the last decreased digit while break_point < length: digits[break_point] = '9' break_point += 1
Example with n = 10
:
- Convert to
['1', '0']
- Violation found at index 1 (since
1 > 0
) - After backward loop:
break_point = 0
, digits =['0', '0']
- Without incrementing: You'd start filling from index 0, resulting in
['9', '0']
→90
(incorrect) - With incrementing: You start filling from index 1, resulting in
['0', '9']
→9
(correct, after removing leading zero)
How does merge sort divide the problem into subproblems?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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
Want a Structured Path to Master System Design Too? Don’t Miss This!