栈之字典序最小的子序列

参考 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 的长度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值