Problem overview
The method RemoveKdigits receives num, a string of digits that stands for a non-negative integer, and an integer k. It must delete exactly k digits from the string, without reordering the rest, so that the number left is as small as possible. It returns that number as a string, with no leading zeros. If nothing remains, or only zeros, the answer is "0".
For "1432219" with k = 3, removing 4, 3 and 2 leaves "1219", the smallest result. For "10200" with k = 1, removing the 1 leaves "0200", which is written "200" once the leading zero goes. For "10" with k = 2, every digit is removed, and the answer is "0".
Approach: drop a digit that is bigger than its right neighbour
Trying every set of k digits to remove is hopeless for strings of up to 100,000 digits. A greedy rule works instead.
Compare the results of removing one digit. The left end of a number matters most, so the first place where two candidates differ decides which is smaller. Look at a digit and the one right after it. If the digit is bigger than its right neighbour, removing it slides the smaller neighbour into its position, and the number gets smaller. If the digit is smaller or equal, removing it never helps at that position.
So the rule is: scan the digits from left to right and, while the digit just kept is bigger than the digit now being read, remove it, as long as removals are left. A stack does this naturally. Its top is the last digit kept:
- For each new digit
c, pop the top whilek > 0and the top is greater thanc. Each pop uses one removal. - Then push
c. It stays until a smaller digit that comes later removes it.
Two leftovers need care:
- If removals remain after the scan, the digits never decrease from bottom to top, as in
"1234". The biggest digits are then at the end, so pop from the top untilkis 0. - The stack can start with zeros, as in
"0200". They are skipped when the answer is written, and an empty result becomes"0".
On "1432219", k = 3: the stack grows to 1, 4. The digit 3 pops 4 (k = 2), the next 2 pops 3 (k = 1), then the second 2 is pushed and the 1 pops one 2 (k = 0). The remaining digits are pushed without further checks, and the stack ends as 1, 2, 1, 9.
Step by step
- Create a
List<char>namedstack. Its end is the top:Addpushes, andRemoveAt(stack.Count - 1)pops. AListis used rather thanStack<char>because the output step reads the digits from bottom to top, and aStackenumerates from the top. - Loop over the digits of
numwithforeach, usingcfor the current one. - Pop while it helps. The inner
whileruns whilek > 0, the stack is not empty and its top is greater thanc. - Remove the top with
RemoveAt(stack.Count - 1). - Decrease
k, since one removal is used. - Push
cwithAdd. - Use the leftover removals. After the scan, a second loop runs while
k > 0. - Pop the top digit in it: the stack never decreases from bottom to top at this point, so the top is the biggest digit.
- Decrease
kagain. - Return the result of a helper,
JoinWithoutLeadingZeros(stack). - Declare the helper as a
privatemethod taking theList<char>and returning astring. - Create
startingZeros, aboolthat istruewhile only zeros have been seen. - Create a
StringBuildernamedresult. Appending to it doesn't copy the whole text each time, as repeated+on strings would. - Loop over the stack with
foreach, which goes from the first element to the last: the order of the digits in the number. - Check for a digit other than
'0'. - Turn
startingZerosoff in that case. It stays off, so the zeros in"200"are kept. - Check
!startingZeros. - Append the digit to
result. - Return
"0"for an empty result andresult.ToString()otherwise, with the ternary operator.
The solution in C#
class Solution {
public string RemoveKdigits(string num, int k) {
List<char> stack = new List<char>();
foreach (char c in num) {
while (k > 0 && stack.Count > 0 && stack[stack.Count - 1] > c) {
stack.RemoveAt(stack.Count - 1);
k--;
}
stack.Add(c);
}
while (k > 0) {
stack.RemoveAt(stack.Count - 1);
k--;
}
return JoinWithoutLeadingZeros(stack);
}
private string JoinWithoutLeadingZeros(List<char> stack) {
bool startingZeros = true;
StringBuilder result = new StringBuilder();
foreach (char ch in stack) {
if (ch != '0') {
startingZeros = false;
}
if (!startingZeros) {
result.Append(ch);
}
}
return result.Length == 0 ? "0" : result.ToString();
}
}
The C# details worth knowing:
- Characters compare by code.
charsupports>and<, and the digit characters'0'to'9'have increasing codes, so'9' > '1'istrue, just like9 > 1. The solution never converts the digits to numbers. RemoveAton the last index is cheap. Removing from the end of aListdoesn't shift any elements. Removing from the front would, and it would make the method slow.foreachover a string gives its characters one by one. It readsnumand doesn't change it, since strings are immutable. The parameterkcan be changed freely: it is a local copy of the argument.List<char>andStringBuilderneed namespaces. They come fromSystem.Collections.GenericandSystem.Text, so a standalone file needs the matchingusinglines.- The ternary operator
cond ? a : bis an expression, so it can sit right afterreturn. Both branches here are strings. - Why the second loop is safe. The constraints give
k <= num.Length, so the stack never runs out of digits to pop. The stack holdsnum.Lengthminus the digits already removed, which is at least thekthat remains.
Complexity
Let n be the length of num.
Time: O(n). Every digit is pushed once and popped at most once, so the two loops together do at most about 2n stack operations. The helper makes one more pass over the stack.
Space: O(n). The stack and the StringBuilder each hold up to n characters.
Edge cases and common mistakes
- Everything removed.
"10"withk = 2empties the stack, and the helper returns"0". - Leading zeros appear after removal.
"10200"withk = 1leaves0, 2, 0, 0on the stack, and the output is"200". Zeros after the first non-zero digit stay. - All zeros left.
"100"withk = 1leaves0, 0, which has no non-zero digit, so the answer is"0". - Increasing digits.
"1234"withk = 2never pops during the scan, and the second loop removes 4 and 3, giving"12". Forgetting that loop returns a string that is too long. - Equal digits.
"1111"withk = 2gives"11". The comparison is strict (>), so equal digits are not popped during the scan, and the second loop removes the last ones. - Decreasing digits.
"54321"withk = 2gives"321": each new digit pops the one before it untilkruns out. - Comparing as numbers. The input can have 100,000 digits, far more than an
intorlongcan hold, so the digits have to stay characters. - Returning an empty string. The answer for no remaining digits is
"0", not"".
Testing
"1432219"withk = 3gives"1219"."10200"withk = 1gives"200"."10"withk = 2gives"0"."1234"withk = 2gives"12"."112"withk = 1gives"11"."10001"withk = 4gives"0".
