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

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

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

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

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

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

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 一类问题
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335