参考 LeetCode
LeetCode 1081. 不同字符的最小子序列
给出一个由a-z组成的字符串S,求他的一个子序列,满足如下条件:
1、包含字符串中所有出现过的字符各1个。
2、是所有满足条件1的串中,字典序最小的。
例如:babbdcc,出现过的字符为:abcd,而包含abcd的所有子序列中,字典序最小的为abdc。
class Solution:
def smallestSubsequence(self, text: str) -> str:
n = len(text)
stack = []
i = 0
while i < n:
curr = text[i]
if curr in stack: # 已经存在,继续遍历下一个
i += 1
continue
while stack and curr < stack[-1]: # 当前比栈顶元素小,并且栈顶元素值在后面会出现,那么可以先把它弹出,后面仍然有机会入栈
if stack[-1] in text[i + 1:]:
stack.pop()
else: # # 当前比栈顶元素小,并且栈顶元素值不会在后面会出现,那么需要保留,因为需要包含所有出现过的值
break
stack.append(curr) # 放入栈中
i += 1 # 继续往下遍历
return ''.join(stack)
其他相似题目:
LeetCode 402. 移掉 K 位数字(中等)
给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。
注意:
num 的长度小于 10002 且 ≥ k。
num 不会包含任何前导零。
示例 1 :
输入: num = "1432219", k = 3
输出: "1219"
解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。
示例 2 :
输入: num = "10200", k = 1
输出: "200"
解释: 移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。
示例 3 :
输入: num = "10", k = 2
输出: "0"
解释: 从原数字移除所有的数字,剩余为空就是 0。
class Solution:
def removeKdigits(self, num: str, k: int) -> str:
stack = []
remain = len(num) - k
for digit in num:
while k and stack and stack[-1] > digit:
stack.pop()
k -= 1
stack.append(digit)
return ''.join(stack[:remain]).lstrip('0') or '0'
时间复杂度:虽然内层还有一个 while 循环,但是由于每个数字最多仅会入栈出栈一次,因此时间复杂度仍然为 O(N)
,其中 NN 为数字长度。
空间复杂度:我们使用了额外的栈来存储数字,因此空间复杂度为 O(N)
,其中 NN 为数字长度。
LeetCode 316. 去除重复字母(困难)
给你一个仅包含小写字母的字符串,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证返回结果的字典序最小(要求不能打乱其他字符的相对位置)。
示例 1:
输入: "bcabc"
输出: "abc"
示例 2:
输入: "cbacdcbc"
输出: "acdb"
思路:
遇到一个新字符 如果比栈顶小 并且在新字符后面还有和栈顶一样的 就把栈顶的字符抛弃了
class Solution:
def removeDuplicateLetters(self, s: str) -> str:
stack = []
visited = []
last_idx = {c : i for i, c in enumerate(s)} # 记录字符最后一次出现的index
for i, c in enumerate(s):
if c not in visited: # 还没出现过
while stack and c < stack[-1] and i < last_idx[stack[-1]]: # 当前字符比栈顶元素小,并且栈顶元素索引比最后一个出现的索引小,也即后面还会有栈顶元素出现,所以可以将其删除
remove_s = stack.pop()
visited.remove(remove_s)
visited.append(c)
stack.append(c)
return ''.join(stack)
LeetCode 321. 拼接最大数(困难)
给定长度分别为 m 和 n 的两个数组,其元素由 0-9 构成,表示两个自然数各位上的数字。现在从这两个数组中选出 k (k <= m + n) 个数字拼接成一个新的数,要求从同一个数组中取出的数字保持其在原数组中的相对顺序。
求满足该条件的最大数。结果返回一个表示该最大数的长度为 k 的数组。
说明: 请尽可能地优化你算法的时间和空间复杂度。
示例 1:
输入:
nums1 = [3, 4, 6, 5]
nums2 = [9, 1, 2, 5, 8, 3]
k = 5
输出:
[9, 8, 6, 5, 3]
示例 2:
输入:
nums1 = [6, 7]
nums2 = [6, 0, 4]
k = 5
输出:
[6, 7, 6, 0, 4]
示例 3:
输入:
nums1 = [3, 9]
nums2 = [8, 9]
k = 3
输出:
[9, 8, 9]
思路:
我们从 nums1 中取了 k1 个,从 num2 中取了 k2 个,其中 k1 + k2 = k。而 k1 和 k2 这 两个子问题我们是会解决的(移掉 K 位数字)。由于这两个子问题是相互独立的,因此我们只需要分别求解,然后将结果合并即可。
- 从 nums1 中 取 min(i, len(nums1))个数形成新的数组 A(移掉 K 位数字),其中 i 等于 0,1,2, … k。
- 从 nums2 中 对应取 min(j, len(nums2))个数形成新的数组 B(移掉 K 位数字),其中 j 等于 k - i。
- 将 A 和 B merge
- 上面我们暴力了 k 种组合情况,我们只需要将 k 种情况取出最大值即可。
class Solution:
def maxNumber(self, nums1: List[int], nums2: List[int], k: int) -> List[int]:
max_res = []
for i in range(k + 1):
if i <= len(nums1) and k - i <= len(nums2):
temp_res = self.merge(self.pick_max(nums1, i), self.pick_max(nums2, k - i))
max_res = max(max_res, temp_res)
return max_res
def pick_max(self, nums, k):
drop = len(nums) - k
stack = []
for num in nums:
while drop and stack and stack[-1] < num:
stack.pop()
drop -= 1
stack.append(num)
return stack[:k]
def merge(self, A, B):
# 如果 A 和 B 是两个数组,当前仅当 A 的首个元素字典序大于 B 的首个元素,A > B 返回 true,否则返回 false。
ans = []
while A or B:
bigger = A if A > B else B
ans.append(bigger[0])
bigger.pop(0)
return ans
时间复杂度:pick_max 的时间复杂度为 O(M + N),其中 M 为 nums1 的长度,N 为 nums2 的长度。 merge 的时间复杂度为 O(k),再加上外层遍历所有的 k 中可能性。因此总的时间复杂度为 O(k^2 * (M + N))。
空间复杂度:我们使用了额外的 stack 和 ans 数组,因此空间复杂度为 O(max(M, N, k)),其中 M 为 nums1 的长度,N 为 nums2 的长度。