LeetCode Cookbook 栈和队列 下篇

LeetCode Cookbook 栈和队列 下篇

   下篇主要也是三个分类:(1)model-IV 使用栈对字符串进行增删改查的处理;(2)model-V使用栈对两个数组进行查找或对比操作;(3)model-VI 使用栈处理数组,其中栈的使用非常灵活,不仅可以储存数组的元素,也可以储存数组的索引、二维数组、树节点等。

844. 比较含退格的字符串(model-IV 编码字符串)

题目链接:844. 比较含退格的字符串
题目大意:给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。注意:如果对空文本输入退格字符,文本继续为空

例如:

输入:s = "ab#c", t = "ad#c"
输出:true
解释:s 和 t 都会变成 "ac"。

输入:s = "a#c", t = "b"
输出:false
解释:s 会变成 "c",但 t 仍然是 "b"

解题思路:用栈处理这道题非常的顺畅。

class Solution:
    def backspaceCompare(self, s: str, t: str) -> bool:

        def build(s: str) -> str:
            stack = list()
            for ch in s:
                if ch != '#':
                    stack.append(ch)
                elif stack:
                    stack.pop()
            return ''.join(stack)

        return build(s) == build(t)

1047. 删除字符串中的所有相邻重复项(model-IV 延展1)

题目链接:1047. 删除字符串中的所有相邻重复项
题目大意:给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。在 S 上反复执行重复项删除操作,直到无法继续删除。在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

例如:

输入:"abbaca"
输出:"ca"
解释:在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"

解题思路: 非常顺畅地一道题 判断栈尾端与下一个元素是否相同,相同则吐出去。

class Solution:
    def removeDuplicates(self, s: str) -> str:
        stack = list()
        for ch in s:
            if stack and ch == stack[-1]:
                stack.pop()
            else:
                stack.append(ch)
            # print(stack)
        return ''.join(stack)

402. 移掉 K 位数字**(model-IV 延展2)

题目链接:402. 移掉 K 位数字
题目大意:给你一个以字符串表示的非负整数 num 和一个整数 k ,移除这个数中的 k 位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字。

例如:

输入:num = "1432219", k = 3
输出:"1219"
解释:移除掉三个数字 4, 3,2 形成一个新的最小的数字 1219 。

输入:num = "10200", k = 1
输出:"200"
解释:移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。

输入:num = "10", k = 2
输出:"0"
解释:从原数字移除所有的数字,剩余为空就是 0

解题思路:这道题不难但细节拉满,需要注意这三种情况:(1)k等于字符串的长度;(2)栈处理中k未递减至0;(3)在链表向字符串转化过程中,前置零与后置零的处理,即:‘0100’与‘000’的处理。这道题可以适当与字符串函数进行关联,可以极大简化答题流程。

class Solution:
    def removeKdigits(self, num: str, k: int) -> str:
        # 细节1 预先判断一下哎省力气
        if len(num)==k: return '0'
        stack = list()
        for ch in num:
            while stack and ch<stack[-1]and k:
                k -= 1
                stack.pop()
            stack.append(ch)
        # 细节2 最后的尾巴需要处理一下
        ans = stack[:-k] if k else stack
        # 细节3 用字符串函数相对于强制转换 更快一些 
        return ''.join(ans).lstrip('0') or '0'
        # return str(int(''.join(ans)))

394. 字符串解码(model-IV 延展3)

题目链接:394. 字符串解码
题目大意:给定一个经过编码的字符串,返回它解码后的字符串。编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

例如:

输入:s = "3[a]2[bc]"
输出:"aaabcbc"

输入:s = "3[a2[c]]"
输出:"accaccacc"

输入:s = "2[abc]3[cd]ef"
输出:"abcabccdcdcdef"

解题思路:本来打算使用 双指针法做这道题发现 对于 示例2中这种 大括号包小括号的情况有些无能为力,栈果然是解析 括号 的有力工具!

class Solution:
    def decodeString(self, s: str) -> str:
        
        stack,ans,num = list(),"",0
        for ch in s:
            # 记录重读的字符段
            if ch == '[':
                stack.append([ans,num])
                # 添加完后记着抹干净
                ans,num = "",0
            # 展开需要重复的字符段
            elif ch == ']':
                last_ans,ans_num = stack.pop()
                ans = last_ans + ans_num*ans
            # 字符段的重复次数 是二位数以上
            elif '0'<=ch<='9':
                num = num*10+int(ch)
            else:
                ans += ch
        return ans

880. 索引处的解码字符串(model-IV 延展4)

题目链接:880. 索引处的解码字符串
题目大意:给定一个编码字符串 S。请你找出 解码字符串 并将其写入磁带。解码时,从编码字符串中 每次读取一个字符 ,并采取以下步骤:

  • 如果所读的字符是字母,则将该字母写在磁带上。
  • 如果所读的字符是数字(例如 d),则整个当前磁带总共会被重复写 d-1 次。
    现在,对于给定的编码字符串 S 和索引 K,查找并返回解码字符串中的第 K 个字母。

例如:

输入:S = "leet2code3", K = 10
输出:"o"
解释:
解码后的字符串为 "leetleetcodeleetleetcodeleetleetcode"。
字符串中的第 10 个字母是 "o"。

输入:S = "ha22", K = 5
输出:"h"
解释:
解码后的字符串为 "hahahaha"。第 5 个字母是 "h"

解题思路: 非常有趣的一道题 倒着来 的考虑思路 记录与 456. 132 模式一种思路。此题先计数然后再倒着解析本题。

class Solution:
    def decodeAtIndex(self, s: str, k: int) -> str:
        # 用到了 栈 的思维
        # 解码后的字符串长度
        size = 0
        for c in s:
            if c.isdigit():
                size *= int(c)
            else:
                size += 1
        
        for c in reversed(s):
            k %= size
            if k == 0 and c.isalpha():
                return c
            
            if c.isdigit():
                size /= int(c)
            else:
                size -= 1

1003. 检查替换后的词是否有效(model-IV 延展5)

题目链接:1003. 检查替换后的词是否有效
题目大意:给你一个字符串 s ,请你判断它是否 有效 。字符串 s 有效 需要满足:假设开始有一个空字符串 t = “” ,你可以执行 任意次 下述操作将 t 转换为 s :
-将字符串 “abc” 插入到 t 中的任意位置。形式上,t 变为 tleft + “abc” + tright,其中 t == tleft + tright 。注意,tleft 和 tright 可能为 空 。
如果字符串 s 有效,则返回 true;否则,返回 false。

例如:

输入:s = "aabcbc"
输出:true
解释:"" -> "abc" -> "aabcbc" 因此,"aabcbc" 有效。

输入:s = "abccba"
输出:false
解释:执行操作无法得到 "abccba"

解题思路: 方法(一) 两个大判断:(1)首先判断字符串中 abc 的数量相等且均为字符串总长度的三分之一;(2)同单调栈对 abc 子串的判断。当然此题也可以直接使用字符串的替换函数进行快速解题,见方法(二)。

方法(一)

class Solution:
    def isValid(self, s: str) -> bool:
        n = len(s)
        counter = collections.Counter(s)
        for ch in counter:
            if counter[ch] != n//3:
                return False 
        stack = []
        for ch in s:
            if ch == 'c':
                idx = 2
                if stack and stack[-1] == 'b':
                    stack.pop()
                else:
                    return False
                if stack and stack[-1] == 'a':
                    stack.pop()
                else:
                    return False
            else:
                stack.append(ch)
        return True

方法(二)

class Solution:
    def isValid(self, s: str) -> bool:
        while 'abc' in s:
            s=s.replace('abc','')
            if s=='':
                return True
        return False

496. 下一个更大元素 I(model-V 双数组)

题目链接:496. 下一个更大元素 I
题目大意:nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。

  • 给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中nums1 是 nums2 的子集。
  • 对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素 。
  • 如果不存在下一个更大元素,那么本次查询的答案是 -1 。返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素 。

例如:

输入:nums1 = [4,1,2], nums2 = [1,3,4,2].
输出:[-1,3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:
- 4 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1- 1 ,用加粗斜体标识,nums2 = [1,3,4,2]。下一个更大元素是 3- 2 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1

解题思路: 结合 哈希表 处理此题。

class Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        stack,hash_map = list(),dict()
        for num in nums2:
            while stack and stack[-1] < num:
                small = stack.pop()
                hash_map[small] = num
            stack.append(num)
        return [hash_map[i] if i in hash_map else -1 for i in nums1 ]

946. 验证栈序列(model-V 扩展1)

题目链接:946. 验证栈序列
题目大意:给定 pushed 和 popped 两个序列,每个序列中的 值都不重复,只有当它们可能是在最初空栈上进行的推入 push 和弹出 pop 操作序列的结果时,返回 true;否则,返回 false 。

例如:

输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1

输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。

解题思路:模拟栈入栈出,这里面需要注意 数组索引的应用,非常的重要!

class Solution:
    def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
        # 现在有点明白这道题到底要说什么?
        # 也就是 给出了入栈和出栈的顺序 需要自己设计一个中间类似转换的承接的数组
        n = len(pushed)
        st = []
        id = 0
        for num in pushed:
            st.append(num)
            while st and st[-1] == popped[id]:
                st.pop()
                id += 1
        return len(st) == 0

456. 132 模式(model-VI 单数组)

题目链接:456. 132 模式
题目大意:给你一个整数数组 nums ,数组中共有 n 个整数。132 模式的子序列 由三个整数 nums[i]、nums[j] 和 nums[k] 组成,并同时满足:i < j < k 和 nums[i] < nums[k] < nums[j] 。如果 nums 中存在 132 模式的子序列 ,返回 true ;否则,返回 false 。

例如:

输入:nums = [1,2,3,4]
输出:false
解释:序列中不存在 132 模式的子序列。

输入:nums = [3,1,4,2]
输出:true
解释:序列中有 1132 模式的子序列: [1, 4, 2]

解题思路: 与 880. 索引处的解码字符串 用到了一样的思路 需要把数组给倒过来 这样处理省力些。

class Solution:
    def find132pattern(self, nums: List[int]) -> bool:
        stack = list()
        n = len(nums)
        k = float(-inf)
        for n in nums[::-1]:
            if n < k:
                return True
            # stack[-1] is num_k ,n is num_j  
            while stack and stack[-1] < n:
                k = max(k,stack.pop())
            stack.append(n)
        return False

503. 下一个更大元素 II(model-VI 扩展1 栈存储索引*)

题目链接:503. 下一个更大元素 II
题目大意:给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1 。

例如:

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

输入: nums = [1,2,3,4,3]
输出: [2,3,4,-1,4]

解题思路 :栈 存储数组的索引 503. 下一个更大元素 II 739. 每日温度 862. 和至少为 K 的最短子数组 84. 柱状图中最大的矩形

class Solution:
    def nextGreaterElements(self, nums: List[int]) -> List[int]:

        n = len(nums)
        ans = [-1]*n
        stack = list()
        for i in range(n*2-1):
            while stack and nums[stack[-1]] < nums[i%n]:
                ans[stack.pop()] = nums[i%n]
            stack.append(i%n)
        return ans

862. 和至少为 K 的最短子数组(model-VI 扩展2 栈存储索引*)

题目链接:862. 和至少为 K 的最短子数组
题目大意:给你一个整数数组 nums 和一个整数 k ,找出 nums 中和至少为 k 的 最短非空子数组 ,并返回该子数组的长度。如果不存在这样的 子数组 ,返回 -1 。子数组 是数组中 连续 的一部分。

例如:

输入:nums = [1], k = 1
输出:1

输入:nums = [2,-1,2], k = 3
输出:3

解题思路: 这道题有点前缀和的意思 有几个细节地方需要好好注意一下!(1)队列的创建需要给个[0]开头,这样快速进行前缀和数组的建立;(2)单调栈一定要动态维护进行过程简化;(3)一定要分清 stack 中存储的到底是数组的元素还是数组的索引,非常容易弄混。

class Solution:
    def shortestSubarray(self, nums: List[int], k: int) -> int:
     
        # 创建前缀和数组 p
        n = len(nums)
        P = list([0])
        for num in nums:
            P.append(P[-1]+num)
        # 有些类似双指针的做法 y-x 的长度和 Py-Px >= k
        ans = n+1
        # 索引数组
        q = collections.deque()
        for y,Py in enumerate(P):
            # 1) 注意第一种 必须满足 Py-Px >= k
            # 注意 单调栈 一定单调递增才行!!!
            while q and Py <= P[q[-1]] :
                q.pop()
            # 2) 从双端队列 q 的最短开始判断
            while q and Py - P[q[0]] >= k:
                ans = min(ans,y-q.popleft())
            q.append(y)
        return ans if ans < n+1 else -1

84. 柱状图中最大的矩形(model-VI 扩展3 栈存储索引*)

题目链接:84. 柱状图中最大的矩形
题目大意:给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。

例如:
在这里插入图片描述

输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10

解题思路:用栈存储 数组索引,维持最小栈。

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        stack = []
        heights = [0] + heights + [0]
        res = 0
        # print('heights:',heights)
        for i in range(len(heights)):
            # print(stack)
            while stack and heights[stack[-1]] > heights[i]:
                tmp = stack.pop()
                # print('i:',i,'tmp:' ,tmp,'heights[tmp]',heights[tmp])
                # print('area:',((i- 1) - stack[-1]) * heights[tmp])
                res = max(res, ((i- 1) - stack[-1]) * heights[tmp])
                # print(res)
            stack.append(i)
        return res

总结

   栈和队列还有一些”应用题“,很有意思,把那几道题当做趣味题做一下记录吧!同时model-VI的扩展题还有几道,也在下篇记录一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值