【LeetCode】滑动窗口、双指针、单调队列和单调栈

目录

 

167. 两数之和 II - 输入有序数组

88. 合并两个有序数组

26. 删除排序数组中的重复项

76. 最小覆盖子串

32. 最长有效括号

155. 最小栈

84. 柱状图中最大的矩形

42. 接雨水

239. 滑动窗口最大值

918. 环形子数组的最大和


167. 两数之和 II - 输入有序数组

思路:

  • 一般双指针的主要思路是先找到暴力解法,然后考虑有没有单调性之类的性质可以利用,再进行优化
  • 使用i,j两个指针,初始位置在0和数组末尾,若相加的结果大于target则说明当前的结果需要减小,则j往前移;若相加的结果小于target则说明当前的结果需要增大,则i往后移。
class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        n = len(numbers)
        i, j = 0, n-1
        while i<j:
            if numbers[i] + numbers[j] < target:
                i += 1
            elif numbers[i] + numbers[j] > target:
                j -= 1
            else:
                return [i+1, j+1]

88. 合并两个有序数组

思路:

  • 这道题就是基本的归并思想
  • 由于题目要求的是把结果存入nums1中,则要注意不能从前开始比较合并,要从后面开始
  • 这里需要注意的是如果p1,p2赋初值为m-1,n-1,可能就取不到下标为0的那个数
class Solution:
    def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
        """
        Do not return anything, modify nums1 in-place instead.
        """
        # nums1的最大数指针 nums2的最大数指针 结果集的最大数指针
        p1, p2, p3 = m, n, m+n
        while p1 and p2:
            if nums1[p1-1] >= nums2[p2-1]:
                nums1[p3-1] = nums1[p1-1]
                p1 -= 1
            else:
                nums1[p3-1] = nums2[p2-1]
                p2 -= 1
            p3 -= 1
        while p2:
            nums1[p3-1] = nums2[p2-1]
            p2 -= 1
            p3 -= 1

26. 删除排序数组中的重复项

思路:

  • 由于不能申请额外空间,考虑直接在数组上进行操作。
  • 设置两个指针,一个指向当前需要存储数的位置k,一个对数组进行扫描i
class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        if not nums:
            return 0
        k = 1
        for i in range(1, len(nums)):
            if nums[i] != nums[i-1]:
                nums[k] = nums[i]
                k += 1
        return k

76. 最小覆盖子串

思路:

  • 首先一样的步骤,考虑暴力解法,会需要O(n^2)的时间复杂度
  • 然后考虑单调性,我们可以使用两个指针,i,j指示答案字符串的首尾。当i逐渐往后移,j必定只能往后增加,不可能往前,因为那样会包含了最佳答案在里面,不可能得到最短的。
  • 这样的话,维护一个hash表,记录t字符串每个字符出现的次数,最后考虑的就是i依次往后遍历,j从起点开始,i往后一格,将hash对应位置-1,j也往后,若当前所指的位置hash表中对应结果小于0了,说明这个字符不在答案中,j往后移
  • 再维护一个答案t所需要几个不同的字符,当都得到了,比较一下当前s[j:i+1]的长度,用res记录最短的即是答案
class Solution:
    def minWindow(self, s: str, t: str) -> str:
        hash_table = {}
        for ch in t:
            hash_table[ch] = hash_table.get(ch, 0) + 1
        cnt = len(hash_table.items())

        res = ""
        c, j = 0, 0
        for i in range(len(s)):
            num = hash_table.get(s[i], 0)
            if num == 1:
                c += 1
            elif num==0:
                hash_table[s[i]] = hash_table.get(s[i], 0)
            hash_table[s[i]] -= 1
            # j从前往后
            while hash_table[s[j]] < 0 and j<i:
                hash_table[s[j]] += 1
                j += 1
            if c==cnt:
                if not res or len(res)>i-j+1:
                    res = s[j:i+1]
        return res

32. 最长有效括号

思路:

  • 首先需要记住括号序列的一个重要性质:假如将(当做数字1,)当做数字-1;则合法的括号序列前缀和一定是>=0的,并且整个序列的最终结果肯定是0
  • start枚举当前这一段的开头,cnt记录前缀和。
  • 若是当前的cnt<0了,说明这一段是不合法的,start=i+1,cnt=0
  • 若是当前的cnt>0,继续做
  • 若是当前的cnt==0,说明当前的这一段是合法的,比较和res的长度,若大于则更新res
  • 注意,为了防止出现((())这种情况,也就是最后的和不会等于0的情况,所以还需要反过来再做一遍
  • (和)的ascii码二进制相差一位,可以直接异或处理
class Solution:
    def longestValidParentheses(self, s: str) -> int:
        reverse_s = list(s[::-1])
        for index, ch in enumerate(reverse_s):
            reverse_s[index] = chr(ord(ch)^1)
        return max(self.work(s), self.work("".join(reverse_s)))
    
    def work(self, s):
        """
        返回当前子串的最长长度
        """
        start, cnt = 0, 0
        n = len(s)
        res = 0
        for i in range(n):
            if s[i] == "(":
                cnt += 1
            else:
                cnt -= 1
                if cnt < 0:
                    start = i+1
                    cnt = 0
                elif cnt == 0:
                    res = max(res, i-start+1)
        return res

155. 最小栈

思路:

  • 这个题有点类似于前缀和,始终要维护一个前缀和的最小数。
  • 用两个栈,一个是模拟栈操作的,一个是始终保存当前栈中最小的数字
class MinStack:

    def __init__(self):
        """
        initialize your data structure here.
        """
        self.stack = []
        self.stack_min = []


    def push(self, x: int) -> None:
        self.stack.append(x)
        if not self.stack_min:
            self.stack_min.append(x)
        else:
            self.stack_min.append(min(self.stack_min[-1], x))

    def pop(self) -> None:
        self.stack.pop()
        self.stack_min.pop()


    def top(self) -> int:
        return self.stack[-1]


    def getMin(self) -> int:
        return self.stack_min[-1]


# Your MinStack object will be instantiated and called as such:
# obj = MinStack()
# obj.push(x)
# obj.pop()
# param_3 = obj.top()
# param_4 = obj.getMin()
  • tips:单调栈(查找每个数左侧第一个比它小的数);单调队列=滑动窗口内的最值

84. 柱状图中最大的矩形

思路:

  • 如何枚举所有的情况?枚举所有柱形的上边界,作为整个矩形的上边界
  • 然后找出左右边界,找出左边离它最近,比它小且最高的矩形;找出右边离它最近,比它小且最高的矩形。
class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        n = len(heights)
        left, right = [0]*n, [0]*n
        stk = []
        for i in range(n):
            while stk and heights[stk[-1]]>=heights[i]:
                stk.pop()
            if not stk:
                left[i] = -1
            else:
                left[i] = stk[-1]
            stk.append(i)
        while stk:
            stk.pop()
        for i in range(n-1, -1, -1):
            while stk and heights[stk[-1]]>=heights[i]:
                stk.pop()
            if not stk:
                right[i] = n
            else:
                right[i] = stk[-1]
            stk.append(i)
        res = 0
        for i in range(n):
            res = max(res, heights[i]*(right[i]-left[i]-1))
        return res

42. 接雨水

思路:

  • 这道题用栈保存当前最小的高度,当出现一个比栈顶高的高度,则弹出进行这一小部分的计算。
  • 整体接雨水的方式就是一小块一小块加起来
class Solution:
    def trap(self, height: List[int]) -> int:
        stk = []
        res = 0
        for i in range(len(height)):
            last = 0
            while stk and height[stk[-1]] <= height[i]:
                t = stk.pop()
                res += (i-t-1) * (height[t]-last)
                last = height[t]
            if stk:
                res += (i-stk[-1]-1)*(height[i]-last)
            stk.append(i)
        return res

239. 滑动窗口最大值

思路:

  • 滑动窗口的最大值主要思路就是保持一个队列,队列中始终保持一个递减的队列。
  • 队头始终是当前窗口最大值,队尾始终是当前窗口最小值。
  • 当队头超过了窗口范围弹出即可。
class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        q, res = [], []
        for i in range(len(nums)):
            if q and i-k>=q[0]:
                q.pop(0)
            while q and nums[i]>=nums[q[-1]]:
                q.pop()
            q.append(i)
            if i-k+1>=0:
                res.append(nums[q[0]])
        return res

918. 环形子数组的最大和

思路:

  • 这类环形数组题,可以考虑的tips是将其展开为一排,展开为2n的一排,这样就变成了控制一个长度为n的滑动窗口,求窗口内的最大值
  • 求前缀和,sum_[i]-sum_[j]就是j+1...i的和,用res记录最大值,时刻更新即可
class Solution:
    def maxSubarraySumCircular(self, A: List[int]) -> int:
        n = len(A)
        for i in range(n):
            A.append(A[i])
        sum_ = [0]*(2*n)
        for i in range(2*n):
            sum_[i] = sum_[i-1] + A[i]
        queue = [0]
        res = sum_[0]
        for i in range(1, 2*n):
            if queue and i - n>queue[0]:
                queue.pop(0)
            if queue:
                res = max(res, sum_[i] - sum_[queue[0]])
            while queue and sum_[i] <= sum_[queue[-1]]:
                queue.pop()
            queue.append(i)
        return res

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值