栈是一种先进后出的数据结构(First In Last Out)
,栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表,这里我不做过多介绍,栈的应用和练习算是面试中的高频考点了,接下来看下我们来看一下Leetcode
关于栈的常见面试题题型,每道题都附上了简单明了的python
解法,大家重点关注算法思想即可
Leetcode1614:括号的最大嵌套深度:简单题 (详情点击链接见原题)
如果字符串满足以下条件之一,则可以称之为 有效括号字符串(
valid parentheses string
,可以简写为VPS
)
题目保证: 给你一个有效括号字符串 s
,遍历字符串 s
,如果遇到一个左括号,那么就将其入栈,如果遇到一个右括号那么就弹出栈顶的左括号与该右括号匹配,这一过程中栈的大小的最大值即为 s
中括号的最大嵌套深度,本题我们甚至无需借助一个辅助栈stack
,直接用一个变量记录最大深度即可
python代码解法:
class Solution:
def maxDepth(self, s: str) -> int:
max_depth = 0
depth = 0
for i in s:
if i == '(':
depth += 1
elif i == ')':
depth -= 1
max_depth = max(depth, max_depth)
return max_depth
Leetcode20:有效的括号:中等题 (详情点击链接见原题)
给定一个只包括
'('
,')'
,'{'
,'}'
,'['
,']'
的字符串s
,判断字符串是否有效
考虑到可能出现的三种不匹配的场景:
case1
:左括号多余 遍历完后栈不为空
case2
:右括号多余 没遍历完栈就为空
case3
:左右括号不匹配 栈顶元素和遍历到的右括号不匹配
python代码解法1:
class Solution:
def isValid(self, s: str) -> bool:
if len(s) % 2 != 0:
return False
stack = []
for i in s:
if i == '(':
stack.append(')')
elif i == '[':
stack.append(']')
elif i == '{':
stack.append('}')
elif not stack and i != stack[-1]: # 情况1和情况3
return False
else:
stack.pop()
return stack == []
python代码解法2:
class Solution:
def isValid(self, s: str) -> bool:
stack = []
for i in s:
if i in ['[', '(', '{']:
stack.append(i)
continue
if stack:
if i == ']' and stack[-1] == '[':
stack.pop(-1)
elif i == '}' and stack[-1] == '{':
stack.pop(-1)
elif i == ')' and stack[-1] == '(':
stack.pop(-1)
else:
break
else:
return False
return stack == []
Leetcode678:有效的括号字符串:中等题 (详情点击链接见原题)
给你一个只包含三种字符的字符串,支持的字符类型分别是
'('
、')'
和'*'
。请你检验这个字符串是否为有效字符串,如果是有效字符串返回true
python代码解法:
class Solution:
def checkValidString(self, s: str) -> bool:
left_stack, star_stack = [], [] # 初始化左括号栈和星栈
for i in range(len(s)):
if s[i] == '(':
left_stack.append(i)
elif s[i] == '*':
star_stack.append(i)
else: # 遇到右括号
if left_stack: # 如果左括号栈不为空
left_stack.pop() # 优先弹出左括号
elif star_stack: # 如果左括号栈为空,星栈不为空
star_stack.pop()
else: # 如果左括号栈和星栈都为空说明右括号不匹配返回False
return False
while left_stack: # 遍历完后发现左括号栈不为空说明有多余的左括号
left_pos = left_stack.pop() # 记录左括号的位置
if star_stack: # 如果星栈不为空说明还可以用星星来匹配左括号
if left_pos < star_stack[-1]:
star_stack.pop()
else:
return False
else:
return False
return True
Leetcode1190:反转每对括号间的子串:中等题
给出一个字符串
s
: 仅含有小写英文字母和括号
请你按照从括号内到外的顺序,逐层反转每对匹配括号中的字符串,并返回最终的结果
方法1:解题思路
1.创建一个存放字符串的辅助栈
stack
以及一个保存当前字符串的变量cur_str
2.遇到(
就将当前的字符串推入栈,并将当前字符串cur_str
设置为空
3.遇到)
就将当前的字符串反转,然后与栈顶元素合并,并将栈顶元素弹出
4.遇到普通的字符就将其添加到当前字符串cur_str
的尾部
python
完整题解代码
class Solution:
def reverseParentheses(self, s: str) -> str:
stack = []
cur_str = ""
for c in s:
if c == '(':
stack.append(cur_str)
cur_str = ""
elif c == ')':
cur_str = stack.pop() + cur_str[::-1]
else:
cur_str += c
return cur_str
方法2解题思路
case1: 只要不是右括号
')'
就直接入栈
case2: 遇到了右括号')'
while
循环抛出栈中遇到'('
之前的字符,append
到tmp
数组中,相当于反转字符串(temp
用来保存左括号和右括号中间的字符串)- 最后将栈中的左括号输出
注:由于栈先进先出的特性,我们将s
中的'('
和')'
中间的字符先push()
到stack
中再pop()
到temp
中,stack
中的栈顶元素为temp
中的栈底元素(即完成括号中间字符的翻转)
python
完整题解代码
class Solution:
def reverseParentheses(self, s: str) -> str:
stack = []
for c in s:
if c == ')': # case2
temp = [] # temp作为辅助栈:暂存括号中间的字符
while stack and stack[-1] != '(':
temp.append(stack.pop())
if stack[-1] == '(':
stack.pop()
stack.extend(temp) # 将中间反转的结果集拼接到stack末尾
else: # case1
stack.append(c)
return "".join(stack)
Leetcode32. 最长有效括号:困难题 (详情点击链接见原题)
给你一个只包含
'('
和')'
的字符串,找出最长有效(格式正确且连续)括号子串的长度
解题思路:先找到所有可以匹配的索引号,然后找出最长连续数列
s = )(()())
,我们用栈可以找到
2
和位置 3
匹配4
和位置 5
匹配1
和位置 6
匹配这个数组为 [2, 3, 4, 5, 1, 6]
,这是通过栈找到的,我们按递增排序,找出该数组的最长连续数列的长度就是最长有效括号长度
python代码解法:
class Solution:
def longestValidParentheses(self, s: str) -> int:
stack = [] # 辅助栈
res = []
for i in range(0, len(s)):
if s[i] == ')':
if stack and s[stack[-1]] == '(':
res += [stack.pop(), i]
else:
stack.append(i)
res.sort()
left, right = 0, 0
ans = 0
while right < len(res):
if res[right - 1] != res[right] - 1:
left = right
right = left
ans = max(ans, right - left + 1)
right += 1
return ans
Leetcode1249. 移除无效的括号:中等题
给你一个由
'('
、')'
和小写字母组成的字符串s
。
你需要从字符串中删除最少数目的'('
或者')'
(可以删除任意位置的括号),使得剩下的「括号字符串」有效
python代码解法:
class Solution:
def minRemoveToMakeValid(self, s: str) -> str:
s = list(s)
stack = []
for i in range(len(s)):
if s[i] == '(': # 1.如果为左括号,将左括号所在下标入栈
stack.append(i)
elif s[i] == ')': # 2. 如果为右括号
if stack: # 2.1如果站中有左括号,此时两括号匹配,弹出前者
stack.pop()
else: # 2.2如果没有左括号,说明右括号多了,需删除,该位子变成空字符串
s[i] = ''
for i in stack: # 3.遍历完成后,将栈中多余的左括号删除
s[i] = ''
return ''.join(s)
Leetcode232:用栈实现队列:简单题 (详情点击链接见原题)
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(
push
、pop
、peek
、empty
):
python代码解法:
class MyQueue:
def __init__(self):
self.stack1 = []
self.stack2 = []
def push(self, x: int) -> None:
self.stack1.append(x)
def pop(self) -> int:
if self.stack2:
return self.stack2.pop()
while self.stack1:
self.stack2.append(self.stack1.pop())
return self.stack2.pop()
def peek(self) -> int:
if self.stack2:
return self.stack2[-1]
while self.stack1:
self.stack2.append(self.stack1.pop())
return self.stack2[-1]
def empty(self) -> bool:
return True if not self.stack1 and self.stack2 else False
Leetcode225:用队列实现栈:简单题 (详情点击链接见原题)
请你仅使用两个队列实现一个后入先出
(LIFO)
的栈,并支持普通栈的全部四种操作(push
、top
、pop
和empty
)
一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外)重新添加到队列尾部,此时再弹出元素就是栈的顺序了
python代码解法:
class MyStack:
def __init__(self):
self.que = deque()
def push(self, x: int) -> None:
self.que.append(x)
def pop(self) -> int:
if self.empty():
return None
for i in range(len(self.que)-1):
self.que.append(self.que.popleft())
return self.que.popleft()
def top(self) -> int:
# 写法一:
# if self.empty():
# return None
# return self.que[-1]
# 写法二:
if self.empty():
return None
for i in range(len(self.que)-1):
self.que.append(self.que.popleft())
temp = self.que.popleft()
self.que.append(temp)
return temp
def empty(self) -> bool:
return not self.que
Leetcode155:最小栈:中等题 (详情点击链接见原题)
设计一个支持
push
,pop
,top
操作,并能在常数时间内检索到最小元素的栈。
python代码解法:
class MinStack:
def __init__(self):
self.stack = [] #
self.min_stack = [] # 栈顶元素为最小
def push(self, val: int) -> None:
self.stack.append(val)
if not self.min_stack:
self.min_stack.append(val)
else:
if val < self.min_stack[-1]:
self.min_stack.append(val)
else:
self.min_stack.append(self.min_stack[-1])
def pop(self) -> None:
self.stack.pop()
self.min_stack.pop()
def top(self) -> int:
return self.stack[-1]
def getMin(self) -> int:
return self.min_stack[-1]
Leetcode144:二叉树的前序遍历:简单题 (详情点击链接见原题)
前序遍历: 根 左 右
即先将根节点放入栈中,然后将右孩子入栈,再加入左孩子,先入栈的后出栈,所以我们要完成根左右的遍历(即出栈顺序),那么入栈顺序即(当根出栈,先右后左入栈)
转载: 动图来自代码随想录
python代码解法:
(本题代码可调试)
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
stack = [root]
result = []
if not root:
return []
while stack:
tree_node = stack.pop()
result.append(tree_node.val) # 保存栈顶元素的值
if tree_node.right: # 1.先检查右孩子节点,若右节点不为空则入栈
stack.append(tree_node.right)
if tree_node.left: # 2.再检查左孩子节点,若左节点不为空则入栈
stack.append(tree_node.left)
return result
if __name__ == '__main__':
s = Solution()
d = TreeNode(5, None, None)
e = TreeNode(6, None, None)
f = TreeNode(7, None, None)
g = TreeNode(8, None, None)
b = TreeNode(3, d, e)
c = TreeNode(4, f, g)
a = TreeNode(1, b, c)
print(s.preorderTraversal(a))
Leetcode145:二叉树的后序遍历:简单题 (详情点击链接见原题)
给你一棵二叉树的根节点
root
,返回其节点值的 后序遍历
相对于前序遍历,前序遍历是根左右,后续遍历是左右根,调整一下前序遍历的代码顺序,变成根右左,再反转 result
数组即可得左右根了
python代码解法:
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
stack = [root]
result = []
if not root:
return []
while stack:
tree_node = stack[-1]
result.append(stack[-1].val)
stack.pop()
if tree_node.left: # 1.先检查左孩子节点,若左节点不为空则入栈
stack.append(tree_node.left)
if tree_node.right: # 2.在检查右孩子节点,若右节点不为空则入栈
stack.append(tree_node.right)
return result[::-1] # 3.反转result数组
Leetcode94:二叉树的中序遍历:简单题 (详情点击链接见原题)
给定一个二叉树的根节点
root
,返回 它的 中序 遍历
解题思路:
为什么前序遍历的代码不能和中序遍历通用呢,因为前序遍历的顺序是中左右,先访问的元素是中间结点,要处理的元素也是中间结点,要访问的元素和要处理的元素顺序是一致的,都是中间结点
中序遍历是左中右,先向左到达二叉树左面的最底部,再开始处理结点
转载: 动图来自代码随想录
python代码解法:
# 中序遍历-迭代
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
ans = []
stack = [] # 用栈来记录我们遍历过的元素
cur = root # 用cur指针来遍历整颗树中的结点
while cur or stack: # 当cur指针不为空,同时栈不为空
if cur:
stack.append(cur)
cur = cur.left
else:
cur = stack.pop()
ans.append(cur.val) # 将弹出的结点值加入结果集
cur = cur.right
return ans
if __name__ == '__main__':
s = Solution()
d = TreeNode(5, None, None)
e = TreeNode(6, None, None)
f = TreeNode(7, None, None)
g = TreeNode(8, None, None)
b = TreeNode(3, d, e)
c = TreeNode(4, f, g)
a = TreeNode(1, b, c)
print(s.inorderTraversal(a))
Leetcode1047:删除字符串中所有相邻重复项:简单题 (详情点击链接见原题)
给出由小写字母组成的字符串
S
,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在S
上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一
python代码解法:
class Solution:
def removeDuplicates(self, s: str) -> str:
stack = []
for i in s:
if stack and i == stack[-1]:
stack.pop(-1)
continue
stack.append(i)
return "".join(stack)
Leetcode150:逆波兰表达式求值:中等题 (详情点击链接见原题)
给你一个字符串数组
tokens
,表示一个根据 逆波兰表示法 表示的算术表达式。请你计算该表达式。返回一个表示表达式值的整数。
对逆波兰表达式不熟悉的同学可以先自行了解一下,这里博主简单介绍一下逆波兰式也叫后缀表达式,我们从小接触的算数运算长这样(a+b)*c-(a+b)/e
,这叫中缀表达式,一个最简单的案例就是中缀表达式(a + b)
转后缀表达式就是ab+
,因为计算机的内存结构是栈式结构,所以对计算机来说,后缀表达式更友好,所以我们如果要计算机帮我们处理复杂的计算首先我们需要把对人类更友好的中缀表达式转为后缀表达式这一步操作
但这道题是直接给我们一个后缀表达式要我们计算结果,这要简单一些
关于后缀表达式的计算,从左往右扫描后缀表达式
1. 初始化一个“操作数栈”:用于存放暂时还不能确定运算次序的操作数
2. 若扫描到操作数则压入栈
3. 若扫描到运算符,则弹出两个栈顶元素执行相应的运算,运算结果压回栈顶 注意:先出栈的是右操作数
python代码解法:
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
stack = [] # 初始化一个操作数栈
symbol = ['*', '+', '-', '/']
for i in tokens:
if i in symbol:
nums1 = stack.pop(-1) # 先出栈的是右操作数
nums2 = stack.pop(-1) # 后出栈的是左操作数
if i == '+':
result = nums1 + nums2
stack.append(result)
elif i == '-':
result = nums2 - nums1 # 右操作数和左操作数的顺序一定不能搞错
stack.append(result)
elif i == '*':
result = nums1 * nums2
stack.append(result)
elif i == '/':
result = nums2 / nums1
if result > 0: # 题目要求:两个整数之间的除法总是 向零截断
result = math.floor(result)
else:
result = math.ceil(result)
stack.append(result)
else:
stack.append(int(i))
return stack[-1] # 返回栈顶元素
Leetcode227. 基本计算器 II:中等题 (详情点击链接见原题)
给你一个字符串表达式
s
,请你实现一个基本计算器来计算并返回它的值。
整数除法仅保留整数部分
python代码解法:
class Solution:
def cal(self, num_stack, op):
right, left = num_stack.pop(), num_stack.pop() # 先出栈的时右操作数,后出栈的时左操作数
ans = 0
if op == '+':
ans = left + right
elif op == '-':
ans = left - right
elif op == '*':
ans = left * right
else:
ans = left // right
num_stack.append(ans)
def calculate(self, s: str) -> int:
op_priority = {'*': 1, '/': 1, '+': 0, '-': 0}
s = '(' + s.replace(' ', '') + ')'
num_stack, op_stack = [], [] # 初始化操作数栈和操作符栈
i = 0
while i < len(s):
if s[i].isdigit(): # 1.遇到数字
num = int(s[i])
while i + 1 < len(s) and s[i + 1].isdigit():
num = num * 10 + int(s[i + 1])
i += 1
num_stack.append(num)
elif s[i] == '(': # 2.遇到左括号直接入栈
op_stack.append(s[i])
elif s[i] == ')': # 3.遇到右括号需弹出符号栈和操作数栈中的元素进行计算
while op_stack and op_stack[-1] != '(':
self.cal(num_stack, op_stack.pop())
else: # 4.当前扫描到操作符
while op_stack and op_stack[-1] != '(':
if op_priority[op_stack[-1]] < op_priority[s[i]]: # 符号栈栈顶元素的优先级大于当前扫描元素的优先级
break
self.cal(num_stack, op_stack.pop())
op_stack.append(s[i])
i += 1
return num_stack[0]
Leetcode224. 基本计算器:困难题 (详情点击链接见原题)
给你一个字符串表达式
s
,请你实现一个基本计算器来计算并返回它的值
python代码解法:
class Solution:
def calculate(self, s: str) -> int:
s = "(" + s.replace(" ", "").replace("(-", "(0-") + ")"
# 1.由于第一个数可能是负数,为减少边界判断,可先往nums中添加一个0
# 2.为防止 () 内出现的首个字符为运算符,将所有空格去掉,并将 (- 替换为(0-
n = len(s)
op_stack, num_stack = [], [] # 操作符栈和操作数栈
i = 0
while i < n:
c = s[i]
i += 1
if c.isdigit(): # 1.遇到数字
num = int(c)
while i < n and s[i].isdigit():
num = num * 10 + int(s[i])
i += 1
num_stack.append(num)
elif c == '(': # 2.遇到左括号直接入栈
op_stack.append(c)
elif c == ')': # 3.遇到右括号需弹出符号栈和操作数栈中的元素进行计算
while op_stack and op_stack[-1] != '(':
self.calc(num_stack, op_stack)
op_stack.pop()
else: # 4.当前扫描到操作符
while op_stack and op_stack[-1] != '(': # 若栈顶元素不是'('则直接进行计算
self.calc(num_stack, op_stack) # '+' 和 '-'同优先级,不存在优先级的比较
op_stack.append(c)
return num_stack[0]
def calc(self, num_stack: list, op_stack: list) -> None:
op, y, x = op_stack.pop(), num_stack.pop(), num_stack.pop() if num_stack else 0
ans = 0
if op == '+':
ans = x + y
elif op == '-':
ans = x - y
num_stack.append(int(ans))
Leetcode1006. 笨阶乘:中等题 (详情点击链接见原题)
我们设计了一个笨阶乘
clumsy
:在整数的递减序列中,我们以一个固定顺序的操作符序列来依次替换原有的乘法操作符:乘法(*)
,除法(/)
,加法(+)
和减法(-)
。例如,clumsy(10) = 10 * 9 / 8 + 7 - 6 * 5 / 4 + 3 - 2 * 1
。然而,这些运算仍然使用通常的算术运算顺序:我们在任何加、减步骤之前执行所有的乘法和除法步骤,并且按从左到右处理乘法和除法步骤
解法1:遇到乘除立即算,遇到加减先入栈
*
或者 /
的计算-
则把当前数组取反放入到栈中python代码解法:
class Solution:
def clumsy(self, n: int) -> int:
if n == 1:
return n
stack = [n] # 辅助栈
symbol = 1
while n - 1 > 0:
if symbol % 4 == 1:
temp = stack.pop()
stack.append(temp * (n - 1))
elif symbol % 4 == 2:
temp = stack.pop()
if temp < 0:
stack.append(-(-temp // (n - 1)))
else:
stack.append(temp // (n - 1))
elif symbol % 4 == 3:
stack.append(n - 1)
else:
stack.append(-(n - 1))
symbol += 1
n -= 1
return sum(stack)
Leetcode71:简化路径:中等题 (详情点击链接见原题)
给你一个字符串
path
,表示指向某一文件或目录的Unix
风格 绝对路径 (以'/'
开头),请你将其转化为更加简洁的规范路径
python代码解法:
class Solution:
def simplifyPath(self, path: str) -> str:
path = path.split('/')
stack = []
for p in path:
if p == '.':
continue
elif p == '..':
if stack:
stack.pop(-1)
else:
continue
elif p != '':
stack.append(p)
return '/' + '/'.join(stack)
Leetcode735. 小行星碰撞:中等题 (详情点击链接见原题)
给定一个整数数组
asteroids
,表示在同一行的小行星。
对于数组中的每一个元素,其绝对值表示小行星的大小,正负表示小行星的移动方向(正表示向右移动,负表示向左移动)。每一颗小行星以相同的速度移动
简单栈模拟运用:由于碰撞抵消总是在相邻行星之间发生,我们可以使用栈来模拟该过程,从前往后处理所有的 asteroids[i]
,使用stack
栈存储当前未被抵消的行星,当栈顶元素方向往右,而当前遍历的元素 asteroids[i]
方向往左时会发生抵消操作
python代码解法:
(解法较为巧妙)
class Solution:
def asteroidCollision(self, asteroids: List[int]) -> List[int]:
stack = []
flag = False
for ast in asteroids:
while stack and ast < 0 and stack[-1] > 0: # 当遍历到的小行星往左移,栈顶元素往右移时
if abs(stack[-1]) > abs(ast): # 栈顶的小行星大于遍历的小行星,跳过遍历下一个
flag = True
break
elif abs(ast) > abs(stack[-1]): # 栈顶的小行星小于遍历的小行星,栈顶小行星出栈
stack.pop()
else: # 栈顶的小行星和正在遍历的小行星一样大,二者相抵消继续遍历下一个行星
flag = True
stack.pop()
break
if flag:
flag = False
continue
stack.append(ast) # 将正在遍历的小行星入栈
return stack
Leetcode394:字符串解码:中等题 (详情点击链接见原题)
博主水平有限,剽窃大佬解题思路,总结K神大佬题解
解题思路
stack
:作为辅助栈用来保存,倍数和中间结果
res
:结果集
算法流程: 遍历字符串 s
中的每个字符 c
case1:当
c
为数字时,将数字字符转化为数字,用于后续的倍数计算
case2:当c
为字母时,在res
的尾部添加c
case3:当c
为[
时,将当前multi
和res
入栈,并分别置空置0
case4:当c
为]
时,stack
出栈,拼接字符串到结果集中
python代码解法:
class Solution:
def decodeString(self, s: str) -> str:
stack, res, multi = [], "", 0 # 初始化
for c in s:
if c == '[': # case3
stack.append([multi, res]) # 先将之前的 multi 和 res 入栈
res, multi = "", 0
elif c == ']': # case4
cur_multi, last_res = stack.pop()
res = last_res + cur_multi * res
elif '0' <= c <= '9': # case1
multi = multi * 10 + int(c)
else: # case2
res += c
return res
Leetcode316:去除重复字母:中等题 (详情点击链接见原题)
给你一个字符串
s
,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置
具体算法
key
为字符c
,value
为其剩余出现次数- 1
1
,我们可以选择丢弃(也可以选择不丢弃)python代码解法1:
from collections import Counter
class Solution:
def removeDuplicateLetters(self, s: str) -> str:
count = Counter(s) # 对s中每个字符的出现次数进行计数
stack = []
for i in s:
if i not in stack:
while stack and i < stack[-1] and count[stack[-1]] > 0: # 保持字典序但也要保持元素的唯一性
stack.pop(-1)
stack.append(i)
count[i] -= 1 # 对已经遍历的元素进行-1操作
return "".join(stack)
Leetcode402:移掉K位数字:中等题 (详情点击链接见原题)
给你一个以字符串表示的非负整数
num
和一个整数k
,移除这个数中的k
位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字
python代码解法:
class Solution:
def removeKdigits(self, num: str, k: int) -> str:
if k == len(num):
return '0'
stack = []
remain = len(num) - k
for i in num:
while stack and stack[-1] > i and k > 0:
stack.pop()
k -= 1
stack.append(i)
return "".join(stack[:remain]).lstrip('0') or '0'