Leetcode 刷题第10天 |20, 150, 1047,239

Leetcode 20有效的括号,150逆波兰表达式,1047删除字符串中邻居元素的重复项 239滑动窗口的最大值

Leetcode 20

题目:

给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。
  • 每个右括号都有一个对应的相同类型的左括号。
    示例 1:
    输入:s = “()”
    输出:true
    示例 2:
    输入:s = “()[]{}”
    输出:true
    示例 3:
    输入:s = “(]”
    输出:false
    提示:
  • 1 <= s.length <= 1 0 4 10^4 104
  • s 仅由括号 ‘()[]{}’ 组成

算法思想:

本题要考虑到三种情况:

  1. ( [ { } ] ( ) —— 找不到对应右括号
  2. [ { ( } } ] —— 左右括号对应不上
  3. { [ ( ) ] } ) ) ) ) —— 有括号多了

主要利用栈这种数据结构,当遇到左括号的时候,入栈一个对应的右括号;当遇到右括号的时候,出栈一个右括号与当前的右括号比较。
第1种情况,当遍历完整个字符串的时候,栈中仍然保存着“(”的对应结果,但此时已经扫描到字符串结尾。
第2种情况,当扫描到“}”的时候,此时栈顶元素为“)”,即“(”的对应结果。不匹配。
第3种情况,当栈中有括号都对应结束之后,字符串仍然没有扫描到结尾。
考虑到主要思想后,算法思想如下:
遍历字符串,遇到“( 或 { 或 [”,入栈对应的“) 或 } 或 ]”,每遇到一次右括号,便出栈一个元素,看是否匹配。

代码实现:

class Solution:
    def isValid(self, s: str) -> bool:
        '''
            本题共计三种情况:
            1.([{}]()       # 左边括号多了
            2.[{(}}]        # 左右括号数量相等,但是左右括号不对齐
            3.{[()]}))))    # 前边左右括号对齐 ,但是右括号多了
        '''
        # 首先来个剪枝的条件
        if len(s) % 2 != 0:
            return False
        stack = [] # 使用栈,省空间
        for item in s:
            if item == '(':
                stack.append(")")
            elif item == '[':
                stack.append(']')
            elif item == "{":
                stack.append("}")
            elif not stack or stack[-1] != item: # 对应第三种情况和第二种情况
                return False
            else:
                stack.pop()
        return True if not stack else False	# 对应第一种情况

或 使用字典,代码如下:

class Solution:
    def isValid(self, s: str) -> bool:
        # 使用字典的方式解决该 问题
        stack = []
        if len(s) % 2 != 0:
            return False
        mapping = {
            '(': ")",
            '[': "]",
            '{': "}"
        }
        for item in s:
            if item in mapping.keys():
                stack.append(mapping[item])
            elif not stack or stack[-1] != item:    # 处理后两种情况
                return False
            else:   # 相等就出栈
                stack.pop()
        return True if not stack else False

Leetcode 150

题目:

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:

  • 有效的算符为 ‘+’、‘-’、‘*’ 和 ‘/’ 。
  • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
  • 两个整数之间的除法总是 向零截断 。
  • 表达式中不含除零运算。
  • 输入是一个根据逆波兰表示法表示的算术表达式。
  • 答案及所有中间计算结果可以用 32 位 整数表示。

示例 1:
输入:tokens = [“2”,“1”,“+”,“3”,““]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
输入:tokens = [“4”,“13”,“5”,”/“,”+“]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:
输入:tokens = [“10”,“6”,“9”,“3”,”+“,”-11","
”,“/”,“*”,“17”,“+”,“5”,“+”]
输出:22
解释:该算式转化为常见的中缀算术表达式为:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22

提示:

  • 1 <= tokens.length <= 1 0 4 10^4 104
  • tokens[i] 是一个算符(“+”、“-”、“*” 或 “/”),或是在范围 [-200, 200] 内的一个整数

算法思想:

正常我们习惯于 3 + 4 ∗ 2 + 3 3+4*2+3 3+42+3这种形式的计算方式,但是这可能带来不正确的计算结果。想到得到正确的计算结果,必须加上括号 ( 3 + 4 ) ∗ ( 2 + 3 ) (3+4)*(2+3) (3+4)(2+3)
什么是逆波兰表达式?
是一种后缀表达式,所谓后缀就是指运算符写在后面。
平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
该算式的逆波兰表达式写法为( ( 1 2 + ) ( 3 4 + ) * ) 。
逆波兰表达式主要有以下两个优点

  • 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
  • 适合用栈操作运算:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。

我们将数字入栈,每遇到一个操作符,出栈两个元素,在将计算结果入栈,直到将最终的计算结果存到栈中。

代码实现:

from operator import add, sub, mul

class Solution:
    def evalRPN(self, tokens) -> int:
        op_map = {
            "+": add,
            "-": sub,
            "*": mul,
            "/": lambda x, y: int(x / y)
        }
        stack = []
        for token in tokens:
            if token not in {'+', "-", "*", "/"}:
                stack.append(token)
            else:
                op2 = stack.pop()
                op1 = stack.pop()
                op2 = int(op2)
                op1 = int(op1)
                stack.append(op_map[token](op1, op2))
        return int(stack.pop())

Leetcode 1047

题目:

给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
示例:
输入:“abbaca”
输出:“ca”
解释:
例如,在 “abbaca” 中,我们可以删除 “bb” 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 “aaca”,其中又只有 “aa” 可以执行重复项删除操作,所以最后的字符串为 “ca”。
提示:

  • 1 <= S.length <= 20000
  • S 仅由小写英文字母组成。

算法思想:

利用栈这种数据结构,栈的作用是:保存遍历过且当前能保留下来的元素。
没扫描一个元素,与当前栈顶的元素比较,如果相同则出栈,否则入栈该元素。直到遍历结束,栈中的元素即位不重复元素。

代码实现:

class Solution:
    def removeDuplicates(self, s: str) -> str:
        # 使用栈模拟删除行为
        # 栈的作用是:保存遍历过且当前能保留下来的元素
        res = list()
        for item in s:
            if res and res[-1] == item:
                res.pop()
            else:
                res.append(item)
        return "".join(res)

或使用双指针,模拟栈的行为,代码如下:

# 方法二,使用双指针模拟栈,如果不让用栈可以作为备选方法。
class Solution:
    def removeDuplicates(self, s: str) -> str:
        res = list(s)
        # 快指针遍历当前所有元素,慢指针记录当前需要保留的元素
        slow = fast = 0
        length = len(res)
        while fast < length:
            res[slow] = res[fast]
            if slow > 0 and res[slow] == res[fast]:
                slow = slow - 1
            else:
                slow = slow + 1
            fast = fast + 1
        return ''.join(res[0: slow])

Leetcode 239

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 & 最大值
[1 3 -1] -3 5 3 6 7 & 3
1 [3 -1 -3] 5 3 6 7 & 3
1 3 [-1 -3 5] 3 6 7 & 5
1 3 -1 [-3 5 3] 6 7 & 5
1 3 -1 -3 [5 3 6] 7 & 6
1 3 -1 -3 5 [3 6 7] & 7
示例 2:
输入:nums = [1], k = 1
输出:[1]
提示:

  • 1 <= nums.length <= 1 0 5 10^5 105
  • -104 <= nums[i] <= 104 104 104
  • 1 <= k <= nums.length

算法思想:

一种简单直观的想法是:每次入队一个元素,入队后取当前队列的最大值,直到遍历所有元素为止。但是取最大值的操作时间复杂度很高。那有没有一种简单的方法?
怎么获取最大值?那么队列中的元素一定是有序的,而且最大值一定放到出口,否则怎么获取最大值呢。问题有来了,队列中的元素都是有序元素了,该如何知道要删除的是哪个元素呢?

其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。这个维护元素单调递减的队列就叫做单调队列
对于push操作,每次出队比当前元素要小的所有元素,直到遇到大于等于自己的元素停止。对于pop操作,每次只出栈当前窗口中的最大值元素,也就是第一个元素,为什么要这样做?
如果窗口大小是3,当前队列中元素是3,2,1,如果第四个元素是0,该入队了,但是当前的窗口位3,该怎么办?因此这种操作的目的是:为了维护当前窗口的公平,否则局部最大值会占据着窗口。

代码实现:

'''
暴力解法:时间复杂度O(n*k)
那有没有更简单的方法实现?
'''
from collections import deque

class MyQueue:  # 自定义单调队列
    def __init__(self) -> None:
        self.queue = deque()
 
    def pop(self, value):
        '''
            这里每次弹出的元素是当前窗口中最大值
        '''
        if self.queue and value == self.queue[0]:
            self.queue.popleft()

    def push(self, value):
        '''
            如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止
        '''
        while self.queue and value > self.queue[-1]:
            self.queue.pop()
        self.queue.append(value)
    def front(self):
        '''
            查询当前队列里的最大值,直接返回队列的最前端元素即可
        '''
        return self.queue[0]
    
class Solution:
    def maxSlidingWindow(self, nums, k: int):
        que = MyQueue()
        result = []
        for i in range(k):
            que.push(nums[i])
        result.append(que.front())
        for i in range(k, len(nums)):
            # 每加入一个新元素,都要先尝试pop一次, 例如 3,2, 1, 最后加入的数字是0
            que.pop(nums[i-k])
            que.push(nums[i])
            result.append(que.front())
        return result
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值