402. Remove K Digits
Problem Description
You are given a string num
that represents a non-negative integer and an integer k
. Your task is to remove exactly k
digits from num
to create the smallest possible integer.
The problem asks you to strategically choose which k
digits to remove so that the remaining digits form the minimum possible number. The digits that remain must keep their original relative order from the input string.
For example:
- If
num = "1432219"
andk = 3
, you could remove the digits '4', '3', and '2' to get"1219"
, which is the smallest possible result - If
num = "10200"
andk = 1
, removing the '1' gives"0200"
which becomes"200"
after removing leading zeros - If
num = "10"
andk = 2
, removing both digits results in an empty string, which should return"0"
The key insight is that to minimize the resulting number, you want to maintain an increasing sequence of digits from left to right as much as possible. When you encounter a digit that is smaller than the previous one, it's beneficial to remove the larger previous digit(s) to make the overall number smaller.
The solution uses a stack-based greedy approach where:
- For each digit in
num
, compare it with the top of the stack - If the current digit is smaller than the stack's top and you still have removals left (
k > 0
), pop the stack and decrementk
- After processing all digits, take only the first
remain
digits (whereremain = len(num) - k
) - Remove any leading zeros and return
"0"
if the result is empty
Intuition
To find the smallest possible number after removing k
digits, we need to understand what makes one number smaller than another. When comparing numbers with the same number of digits, the leftmost (most significant) digits matter the most. For instance, 1999
is smaller than 2000
because the first digit 1
is smaller than 2
.
This leads us to a key observation: we want the smallest possible digits in the leftmost positions. When scanning through the string from left to right, if we encounter a digit that is smaller than the previous one, we should consider removing the previous (larger) digit to make room for the smaller one in a more significant position.
Consider num = "1432219"
with k = 3
. As we scan:
- We see
1
, keep it - We see
4
, which is larger than1
, keep it for now - We see
3
, which is smaller than4
. Here's the crucial insight: removing4
and keeping3
gives us a smaller number because13...
is smaller than14...
- We see
2
, which is smaller than3
. Similarly,12...
is better than13...
This suggests a greedy strategy: maintain a monotonically increasing sequence. Whenever we find a digit that breaks this pattern (i.e., it's smaller than the previous one), we remove the previous larger digits as long as we have removals remaining.
The stack data structure naturally fits this pattern. We can push digits onto the stack when they maintain the increasing order, and pop from the stack when we find a smaller digit that would create a better (smaller) result. This gives us the optimal local decision at each step, which leads to the globally optimal solution.
After processing all digits, if we still have removals left (when the input was already in increasing order), we simply take the first n - k
digits. Finally, we handle edge cases like leading zeros and empty results.
Solution Approach
The implementation uses a greedy algorithm with a stack to maintain the smallest possible number at each step.
Here's how the algorithm works:
-
Initialize a stack to build our result and calculate
remain = len(num) - k
to know how many digits we need to keep. -
Iterate through each character
c
in the input stringnum
:- While we still have removals left (
k > 0
), the stack is not empty, and the top element of the stack is greater than the current characterc
:- Pop the stack (remove the larger digit)
- Decrement
k
by 1
- Append the current character
c
to the stack
- While we still have removals left (
-
Handle remaining removals: After processing all digits, if
k > 0
, it means the input was already in non-decreasing order. We only keep the firstremain
digits by slicing:stk[:remain]
-
Format the result:
- Join the stack elements to form a string
- Remove leading zeros using
lstrip('0')
- Return
'0'
if the result is empty (all digits were removed or only zeros remained)
Let's trace through an example with num = "1432219"
and k = 3
:
- Start with empty stack
[]
- Process
'1'
: stack becomes['1']
- Process
'4'
:'4' > '1'
, so append:['1', '4']
- Process
'3'
:'3' < '4'
, pop'4'
,k = 2
:['1', '3']
- Process
'2'
:'2' < '3'
, pop'3'
,k = 1
:['1', '2']
- Process
'2'
: equal to top, append:['1', '2', '2']
- Process
'1'
:'1' < '2'
, pop'2'
,k = 0
:['1', '2', '1']
- Process
'9'
:k = 0
, just append:['1', '2', '1', '9']
- Result:
"1219"
The time complexity is O(n)
where n
is the length of the input string, as each digit is pushed and popped at most once. The space complexity is O(n)
for the stack.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with num = "14325"
and k = 2
to illustrate the solution approach.
Goal: Remove 2 digits to get the smallest possible number.
Step-by-step process:
-
Initialize:
- Stack:
[]
remain = 5 - 2 = 3
(we need to keep 3 digits)k = 2
(removals left)
- Stack:
-
Process '1':
- Stack is empty, so push '1'
- Stack:
['1']
,k = 2
-
Process '4':
- '4' > '1' (top of stack), so push '4'
- Stack:
['1', '4']
,k = 2
-
Process '3':
- '3' < '4' (top of stack) and
k > 0
- Pop '4' from stack, decrement k
- Stack:
['1']
,k = 1
- Now push '3'
- Stack:
['1', '3']
,k = 1
- '3' < '4' (top of stack) and
-
Process '2':
- '2' < '3' (top of stack) and
k > 0
- Pop '3' from stack, decrement k
- Stack:
['1']
,k = 0
- Now push '2'
- Stack:
['1', '2']
,k = 0
- '2' < '3' (top of stack) and
-
Process '5':
k = 0
(no removals left), so just push '5'- Stack:
['1', '2', '5']
,k = 0
-
Final processing:
- We've used all removals (
k = 0
) - Stack has exactly
remain = 3
digits - Join stack:
"125"
- No leading zeros to remove
- Result: "125"
- We've used all removals (
Why this works: By removing the digits '4' and '3', we allowed smaller digits '2' and '5' to occupy more significant positions, creating the smallest possible 3-digit number from the original string while maintaining the relative order of the remaining digits.
Solution Implementation
1class Solution:
2 def removeKdigits(self, num: str, k: int) -> str:
3 # Use a stack to build the smallest possible number
4 stack = []
5 # Calculate how many digits we need to keep in the final result
6 remaining_digits = len(num) - k
7
8 # Iterate through each digit in the input number
9 for digit in num:
10 # While we still have digits to remove (k > 0)
11 # and the stack is not empty
12 # and the top of stack is greater than current digit
13 # Remove the larger digit from stack to maintain smallest number
14 while k > 0 and stack and stack[-1] > digit:
15 stack.pop()
16 k -= 1
17
18 # Add current digit to the stack
19 stack.append(digit)
20
21 # Take only the required number of digits from the stack
22 # Remove leading zeros and return '0' if the result is empty
23 result = ''.join(stack[:remaining_digits]).lstrip('0')
24 return result if result else '0'
25
1class Solution {
2 public String removeKdigits(String num, int k) {
3 // Use StringBuilder as a stack to build the result
4 StringBuilder stack = new StringBuilder();
5
6 // Iterate through each digit in the input number
7 for (char currentDigit : num.toCharArray()) {
8 // Remove larger digits from the stack when a smaller digit is found
9 // This ensures we get the smallest possible number
10 while (k > 0 && stack.length() > 0 && stack.charAt(stack.length() - 1) > currentDigit) {
11 stack.deleteCharAt(stack.length() - 1);
12 k--;
13 }
14 // Add the current digit to the stack
15 stack.append(currentDigit);
16 }
17
18 // If there are still digits to remove, remove them from the end
19 // This handles cases where the number is already in ascending order
20 while (k > 0) {
21 stack.deleteCharAt(stack.length() - 1);
22 k--;
23 }
24
25 // Remove leading zeros from the result
26 int leadingZeroIndex = 0;
27 while (leadingZeroIndex < stack.length() && stack.charAt(leadingZeroIndex) == '0') {
28 leadingZeroIndex++;
29 }
30
31 // Extract the final result without leading zeros
32 String result = stack.substring(leadingZeroIndex);
33
34 // Return "0" if the result is empty, otherwise return the result
35 return result.isEmpty() ? "0" : result;
36 }
37}
38
1class Solution {
2public:
3 string removeKdigits(string num, int k) {
4 // Use a string as a monotonic stack to build the smallest number
5 string stack;
6
7 // Iterate through each digit in the input number
8 for (char& digit : num) {
9 // While we can still remove digits (k > 0) and the stack is not empty
10 // and the top of stack is greater than current digit
11 // Remove the larger digit from stack to maintain increasing order
12 while (k > 0 && !stack.empty() && stack.back() > digit) {
13 stack.pop_back();
14 k--;
15 }
16 // Add current digit to the stack
17 stack.push_back(digit);
18 }
19
20 // If we still need to remove more digits, remove from the end
21 // This handles cases where the number is already in increasing order
22 while (k > 0) {
23 stack.pop_back();
24 k--;
25 }
26
27 // Find the first non-zero digit position to remove leading zeros
28 int startIndex = 0;
29 while (startIndex < stack.size() && stack[startIndex] == '0') {
30 startIndex++;
31 }
32
33 // Extract the result substring without leading zeros
34 string result = stack.substr(startIndex);
35
36 // If the result is empty (all digits were zeros or removed), return "0"
37 return result.empty() ? "0" : result;
38 }
39};
40
1/**
2 * Removes k digits from the number string to create the smallest possible number
3 * @param num - The input number as a string
4 * @param k - The number of digits to remove
5 * @returns The smallest number after removing k digits
6 */
7function removeKdigits(num: string, k: number): string {
8 // Stack to build the result by maintaining monotonic increasing order
9 const stack: string[] = [];
10
11 // Iterate through each digit in the input number
12 for (const digit of num) {
13 // Remove larger digits from stack when current digit is smaller
14 // This ensures we get the smallest possible number
15 while (k > 0 && stack.length > 0 && stack[stack.length - 1] > digit) {
16 stack.pop();
17 k--;
18 }
19 // Add current digit to the stack
20 stack.push(digit);
21 }
22
23 // If we still need to remove more digits, remove from the end
24 // This happens when the number is in non-decreasing order
25 while (k > 0) {
26 stack.pop();
27 k--;
28 }
29
30 // Join the stack to form the result string and remove leading zeros
31 // Return '0' if the result is empty after removing leading zeros
32 const result: string = stack.join('').replace(/^0*/g, '');
33 return result || '0';
34}
35
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of the input string num
.
- The main loop iterates through each character in the string exactly once:
O(n)
- The inner while loop might seem to add complexity, but each element can be pushed and popped from the stack at most once throughout the entire execution
- Therefore, the total number of operations across all iterations is at most
2n
(each element pushed once and popped at most once) - The
lstrip('0')
operation at the end takesO(n)
in the worst case - Overall time complexity remains
O(n)
Space Complexity: O(n)
where n
is the length of the input string num
.
- The stack
stk
can store at mostn
characters (when no digits are removed) - The final string creation using
''.join(stk[:remain])
creates a new string of size at mostn
- Therefore, the space complexity is
O(n)
Common Pitfalls
1. Forgetting to Handle Remaining Removals
One of the most common mistakes is assuming that all k
removals will be used during the main iteration. When the input digits are already in non-decreasing order (like "12345"), the while loop condition stack[-1] > digit
will never be true, leaving k > 0
after processing all digits.
Incorrect approach:
def removeKdigits(self, num: str, k: int) -> str:
stack = []
for digit in num:
while k > 0 and stack and stack[-1] > digit:
stack.pop()
k -= 1
stack.append(digit)
# Bug: Not handling remaining k
result = ''.join(stack).lstrip('0')
return result if result else '0'
Correct solution:
Always slice the stack to keep only remaining_digits
elements, which handles any unused removals:
remaining_digits = len(num) - k
# ... main loop ...
result = ''.join(stack[:remaining_digits]).lstrip('0')
2. Incorrectly Handling Leading Zeros
Another pitfall is removing leading zeros before taking the correct number of digits, or forgetting to handle the edge case where all remaining digits are zeros.
Incorrect approach:
# Bug: Removing zeros before slicing result = ''.join(stack).lstrip('0')[:remaining_digits]
This would incorrectly count the removed zeros as part of the slice operation.
Correct solution: First slice to get the correct number of digits, then remove leading zeros:
result = ''.join(stack[:remaining_digits]).lstrip('0') return result if result else '0' # Return '0' if empty
3. Not Returning "0" for Empty Results
When all digits are removed or only zeros remain after removing leading zeros, the result string becomes empty. Failing to handle this case will return an empty string instead of "0".
Test case example:
- Input:
num = "10", k = 2
→ Both digits removed → Should return "0", not "" - Input:
num = "1000", k = 1
→ Remove '1' → "000" → Should return "0", not ""
Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.
Which of the following method should we use to solve this problem?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
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
Monotonic Stack Deque Intro If you'd prefer a video div class responsive iframe iframe src https www youtube com embed Dq_ObZwTY_Q title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div The word monotonic means a list or
Want a Structured Path to Master System Design Too? Don’t Miss This!