leetcode - 单调栈

496. 下一个更大元素 I

题目描述

给定两个没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。
找到 nums1 中每个元素在 nums2 中的下一个比其大的值。 
nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个
比 x 大的元素。如果不存在,对应位置输出 -1 。 

示例 1: 
输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
对于num1中的数字4,你无法在第二个数组中找到下一个更大的数字,因此输出 -1。
对于num1中的数字1,第二个数组中数字1右边的下一个较大数字是 3。
对于num1中的数字2,第二个数组中没有下一个更大的数字,因此输出 -1。 

示例 2: 
输入: nums1 = [2,4], nums2 = [1,2,3,4].
输出: [3,-1]
解释:
对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。
对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出 -1 。

提示: 
nums1和nums2中所有元素是唯一的。 
nums1和nums2 的数组大小都不超过1000。 

Related Topics 栈 

暴力解法最容易想到,而且相对来说理解简单:

class Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        # 暴力解法
        res = []
        for num1 in nums1:
            for i in range(nums2.index(num1)+1, len(nums2)):
                if nums2[i] > num1:
                    res.append(nums2[i])
                    break
            else:
                res.append(-1)
        return res

其时间复杂度为 ,而优化方法便是单调栈。若把数组看成不同高度的人的排队结果,每个元素的下一个更大的元素便是他回头看到的第一个高个子。中间的矮个儿即可忽略(也即被挡住)。
解题中需要注意的是入栈顺序是倒叙。因此下一个元素入栈前比其小的元素均要出栈,因为这些元素均是在这个元素的后面,这个元素入栈后比其小的元素均被其挡住。

class Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        hashmap = {}
        stack = []
        for i in range(len(nums2)-1, -1, -1):
            while stack and stack[-1] < nums2[i]:
                stack.pop()
            hashmap[nums2[i]] = -1 if not stack else stack[-1]
            stack.append(nums2[i])
        # print(hashmap)
        res = []
        for num in nums1:
            res.append(hashmap[num])
        return res

单调栈解法时间复杂度为 ,因为对于每个元素来说,最多只有入栈和出栈两个操作。

503. 下一个更大元素 II

题目描述

给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。
数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循
环地搜索它的下一个更大的数。如果不存在,则输出 -1。 

示例 1: 
输入: [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数; 
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。

注意: 输入数组的长度不会超过 10000。 
Related Topics 栈 

对于循环数组元素的下一个元素就不仅仅是其右边的元素了,也可能是其左边的元素。为了实现能同时搜索其左右元素,我们可以将原数组复制一份加入到原数组的后面,然后按照 496. 下一个更大元素 I 的解法:

class Solution:
    def nextGreaterElements(self, nums: List[int]) -> List[int]:
        # 利用 496.下一个更大元素I 中的解法,将数组复制一份加入到原数组后面
        stack = []
        nums = nums + nums
        n = len(nums)
        res = [-1] * n
        for i in range(n-1, -1, -1):
            while stack and stack[-1] <= nums[i]:
                stack.pop()
            res[i] = -1 if not stack else stack[-1]
            stack.append(nums[i])
        # print(res)
        return res[:n//2]

在实际中面对循环数组,一般是采用取余 (%) 来实现。所以本题中我们也可以不复制数组,而采用取余来实现:

class Solution:
    def nextGreaterElements(self, nums: List[int]) -> List[int]:
        stack = []
        n = len(nums)
        res = [-1] * n
        for i in range(2*n-1, -1, -1):
            while stack and stack[-1] <= nums[i % n]:
                stack.pop()
            res[i % n] = -1 if not stack else stack[-1]
            stack.append(nums[i % n])
        # print(res)
        return res

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。
 
Related Topics 栈 贪心算法 

题目分析
首先我们需要确定什么样的数字应该被移除。当我们考察位置 的数字时,若其前面有数字,如果移除前面的数字能获得更小的结果,那前面的数字应该被移除。由于每次对比都是临近的数字,则考虑使用栈来解决问题。

class Solution:
    def removeKdigits(self, num: str, k: int) -> str:
        n = len(num)
        if n == k:
            return '0'
        stack = []
        for c in num:
            while stack and stack[-1] > c and k > 0:
                stack.pop()
                k -= 1
            if not stack and c == '0':
                continue
            stack.append(c)
        # 还有要删除的元素,就从栈顶删除
        for _ in range(k):
            stack.pop()
        return '0' if not stack else ''.join(stack)

1081. 不同字符的最小子序列

题目描述

返回字符串 text 中按字典序排列最小的子序列,该子序列包含 text 中所有不同字符一次。 

示例 1: 
输入:"cdadabcc"
输出:"adbc"

示例 2: 
输入:"abcd"
输出:"abcd"
 
示例 3: 
输入:"ecbacba"
输出:"eacb"

示例 4: 
输入:"leetcode"
输出:"letcod"

提示: 
1 <= text.length <= 1000 
text 由小写英文字母组成 

注意:本题目与 316 https://leetcode-cn.com/problems/remove-duplicate-letters/ 相同 
Related Topics 栈 贪心算法 字符串 

题目分析
本题需要考虑的重点是怎么确定该字符是否需要删除。如果后面还有该字符,则前面比当前大的字符应该被删除,以保留字典序最小的结果。鉴于此,我们考虑对所有字符进行计数,用于判断后面是否还有该字符。

class Solution:
    def smallestSubsequence(self, s: str) -> str:
        # 计数
        hashmap = {}
        for c in s:
            hashmap.setdefault(c, 0)
            hashmap[c] += 1
        stack = []
        inStack = {}
        for c in s:
            hashmap[c] -= 1
            if inStack.get(c):
                continue
            while stack and stack[-1] > c:
                if hashmap[stack[-1]] == 0:
                    break
                inStack[stack.pop()] = False
            stack.append(c)
            inStack[c] = True
        return ''.join(stack)

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] 
Related Topics 贪心算法 动态规划 

本题留着挑战吧。

参考

  1. 单调栈解决 Next Greater Number 一类问题

你可能感兴趣的:(leetcode - 单调栈)