2021-2

单调栈

42. 接雨水(困难)

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例 1

https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/10/22/rainwatertrap.png

上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。

示例:

输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6

方法:单调递减栈

用栈来跟踪可能储水的最长的条形块。使用栈就可以在一次遍历内完成计算。

在遍历数组时维护一个栈。如果当前的条形块小于或等于栈顶的条形块,将条形块的索引入栈,意思是当前的条形块被栈中的前一个条形块界定。如果发现一个条形块长于栈顶,我们可以确定栈顶的条形块被当前条形块和栈的前一个条形块界定,因此我们可以弹出栈顶元素并且累加答案到ans 。

 

算法

使用栈来存储条形块的索引下标。

遍历数组:

(1)当栈非空且 height[current]>height[st.top()]

意味着栈中元素可以被弹出。弹出栈顶元素 top。

计算当前元素和栈顶元素的距离,准备进行填充操作

distance=current−st.top()−1

找出界定高度

bounded_height=min(height[current],height[st.top()])−height[top]

往答案中累加积水量ans+=distance×bounded_height

(2)将当前索引下标入栈

(3)将current 移动到下个位置

即:

(1)当待入栈元素大于栈顶元素时,循环弹出栈顶元素,并计算面积;

(2)否则,入栈。

class Solution:

    def trap(self, height: List[int]) -> int:

        n = len(height)

        if n < 3:

            return 0

        idx, res = 00

        stack = []

        while idx < n:

            while len(stack) > 0 and height[idx] > height[stack[-1]]:

                top = stack.pop()

                if len(stack) == 0:

                    break

                h = min(height[idx], height[stack[-1]]) - height[top]

                dist = idx - stack[-1-1 

                res += h*dist

            stack.append(idx)

            idx += 1

 

        return res  

84. 柱状图中最大的矩形(困难)

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

 

求在该柱状图中,能够勾勒出来的矩形的最大面积。

 

以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。

 

图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。

 

示例:

输入: [2,1,5,6,2,3]

输出: 10

方法:单调栈

参考

https://leetcode-cn.com/problems/largest-rectangle-in-histogram/solution/zhu-zhuang-tu-zhong-zui-da-de-ju-xing-by-leetcode-/

思路

例子

我们用一个具体的例子 [6, 7, 5, 2, 4, 5, 9, 3][6,7,5,2,4,5,9,3] 来帮助读者理解单调栈。我们需要求出每一根柱子的左侧且最近的小于其高度的柱子。初始时的栈为空。

我们枚举 66,因为栈为空,所以 66 左侧的柱子是「哨兵」,位置为 -1。随后我们将 66 入栈。

栈:[6(0)]。(这里括号内的数字表示柱子在原数组中的位置)

我们枚举 77,由于 6<76<7,因此不会移除栈顶元素,所以 77 左侧的柱子是 66,位置为 00。随后我们将 77 入栈。

栈:[6(0), 7(1)]

我们枚举 55,由于 7\geq 57≥5,因此移除栈顶元素 77。同样地,6 \geq 56≥5,再移除栈顶元素 66。此时栈为空,所以 55 左侧的柱子是「哨兵」,位置为 -1−1。随后我们将 55 入栈。

栈:[5(2)]

接下来的枚举过程也大同小异。我们枚举 22,移除栈顶元素 55,得到 22 左侧的柱子是「哨兵」,位置为 -1−1。将 22 入栈。

栈:[2(3)]

我们枚举 44,55 和 99,都不会移除任何栈顶元素,得到它们左侧的柱子分别是 22,44 和 55,位置分别为 33,44 和 55。将它们入栈。

栈:[2(3), 4(4), 5(5), 9(6)]

我们枚举 33,依次移除栈顶元素 99,55 和 44,得到 33 左侧的柱子是 22,位置为 33。将 33 入栈。

栈:[2(3), 3(7)]

这样以来,我们得到它们左侧的柱子编号分别为 [-1, 0, -1, -1, 3, 4, 5, 3][−1,0,−1,−1,3,4,5,3]。用相同的方法,我们从右向左进行遍历,也可以得到它们右侧的柱子编号分别为 [2, 2, 3, 8, 7, 7, 7, 8][2,2,3,8,7,7,7,8],这里我们将位置 88 看作「哨兵」。

在得到了左右两侧的柱子之后,我们就可以计算出每根柱子对应的左右边界,并求出答案了。

class Solution:

    def largestRectangleArea(self, heights: List[int]) -> int:

        if not heights:

            return 0

        n = len(heights)

        left, right = [0] * n, [0] * n

        mono_stack = []

        for i in range(n):

            while mono_stack and heights[mono_stack[-1]] >= heights[i]:

                mono_stack.pop()

            left[i] = mono_stack[-1if mono_stack else -1

            mono_stack.append(i)

        

        mono_stack = []

        for i in range(n-1-1-1):

            while mono_stack and heights[mono_stack[-1]] >= heights[i]:

                mono_stack.pop()

            right[i] = mono_stack[-1if mono_stack else n

            mono_stack.append(i)

 

        res = max((right[i]-left[i]-1)*heights[i] for i in range(n))

        return res

方法2:单调栈(方法1优化)

class Solution:

    def largestRectangleArea(self, heights: List[int]) -> int:

        if not heights:

            return 0

        n = len(heights)

        left, right = [0]*n, [n]*n

        stack = []

        for i in range(n):

            while stack and heights[stack[-1]] >= heights[i]:

                right[stack[-1]] = i

                stack.pop()

            left[i] = stack[-1if stack else -1

            stack.append(i)

        ans = max((right[i]-left[i]-1)*heights[i] for i in range(n))

        return ans

 

并查集

滑动窗口(11道)

159

727

1100

340

1151

剑指 Offer 42. 连续子数组的最大和(简单)

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

要求时间复杂度为O(n)。

 

示例1:

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

输出: 6

解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

 

提示:

1 <= arr.length <= 10^5

-100 <= arr[i] <= 100

方法:DP

 

复杂度分析:

时间复杂度 O(N) 线性遍历数组 nums 即可获得结果,使用 O(N) 时间。

空间复杂度 O(1) 使用常数大小的额外空间。

class Solution:

    def maxSubArray(self, nums: List[int]) -> int:

        for i in range(1len(nums)):

            nums[i] += max(nums[i-1], 0)

        return max(nums)

209. 长度最小的子数组 (中等)

给定一个含有 个正整数的数组和一个正整数 s 找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度如果不存在符合条件的子数组,返回 0。

示例:

输入:s = 7, nums = [2,3,1,2,4,3]

输出:2

解释:子数组 [4,3] 是该条件下的长度最小的子数组。

 

进阶:

如果你已经完成了 O(n) 时间复杂度的解法, 请尝试 O(n log n) 时间复杂度的解法。

方法:滑动窗口

定义两个指针 start 和 end 分别表示子数组的开始位置和结束位置,维护变量sum 存储子数组中的元素和(即从nums[start] 到 nums[end] 的元素和)。

 

初始状态下,start 和 end 都指向下标 0,sum 的值为 0。

每一轮迭代,将nums[end] 加到 sum,如果 sum≥s,则更新子数组的最小长度(此时子数组的长度是 end−start+1),然后将 nums[start] 从 sum 中减去并将start 右移,直到sum<s,在此过程中同样更新子数组的最小长度。在每一轮迭代的最后,将 end 右移。

注意:数组全部和小于s的情况,返回0

class Solution:

    def minSubArrayLen(self, s: int, nums: List[int]) -> int:

        n = len(nums)

        start, sum = 00

        ans = n+1

        for i in range(n):

            sum += nums[i]

            while sum >= s:

                ans = min(ans, i-start+1)

                sum -= nums[start]

                start += 1

        return 0 if ans == n+1 else ans

 

1004. 最大连续1的个数 III(中等)

给定一个由若干 0 和 1 组成的数组 A,我们最多可以将 K 个值从 0 变成 1 。

返回仅包含 1 的最长(连续)子数组的长度。

示例 1

输入:A = [1,1,1,0,0,0,1,1,1,1,0], K = 2

输出:6

解释:

[1,1,1,0,0,1,1,1,1,1,1]

粗体数字从 0 翻转到 1,最长的子数组长度为 6。

示例 2

输入:A = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3

输出:10

解释:

[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]

粗体数字从 0 翻转到 1,最长的子数组长度为 10。

 

提示:

1 <= A.length <= 20000

0 <= K <= A.length

A[i] 为 0 或 1 

方法:滑动窗口

需要记录下加入窗口的是0还是1:

如果是1,我们什么都不用做

如果是0,我们将K减1

 

相应地,我们需要记录移除窗口的是0还是1:

如果是1,我们什么都不做

如果是0,将K 加1

这题就是求最大的窗口。所以窗口变小是没有意义的。

1)窗口增大:left不变,right右移,即right++

什么时候增大?窗口内的0,数量没有达到上限K

2)窗口不变:left跟着right右移,即left++,right++

什么时候不变?窗口内的0,数量达到了上限K

class Solution:

    def longestOnes(self, A: List[int], K: int) -> int:

        n = len(A)

        left = 0

        for j in range(n):

            if A[j] == 0:

                K -= 1

            if K < 0:

                if A[left] == 0:

                    K += 1

                left += 1

        return j-left+1 

1208. 尽可能使字符串相等(中等)

给你两个长度相同的字符串,s 和 t

将 s 中的第 i 个字符变到 t 中的第 i 个字符需要 |s[i] - t[i]| 的开销(开销可能为 0),也就是两个字符的 ASCII 码值的差的绝对值。

用于变更字符串的最大预算是 maxCost。在转化字符串时,总开销应当小于等于该预算,这也意味着字符串的转化可能是不完全的。

如果你可以将 s 的子字符串转化为它在 t 中对应的子字符串,则返回可以转化的最大长度。

如果 s 中没有子字符串可以转化成 t 中对应的子字符串,则返回 0

 

示例 1

输入:s = "abcd", t = "bcdf", cost = 3

输出:3

解释:s 中的 "abc" 可以变为 "bcd"。开销为 3,所以最大长度为 3。

示例 2

输入:s = "abcd", t = "cdef", cost = 3

输出:1

解释:s 中的任一字符要想变成 t 中对应的字符,其开销都是 2。因此,最大长度为 1

示例 3

输入:s = "abcd", t = "acde", cost = 0

输出:1

解释:你无法作出任何改动,所以最大长度为 1。

 

提示:

1 <= s.length, t.length <= 10^5

0 <= maxCost <= 10^6

s 和 t 都只含小写英文字母。

方法:滑动窗口

对于每一对下标相等的字符,s[i]和t[i],把它们转化成相等的 cost 是已知的,

cost = abs(ord(t[i]) - ord(s[i])),

接着问题就转化为:

在一个数组中,在连续子数组的和小于等于 maxCost 的情况下,找到最长的连续子数组长度。

class Solution:

    def equalSubstring(self, s: str, t: str, maxCost: int) -> int:

        n = len(s)

        start, sum = 00

        res = 0

        for i in range(n): 

            sum += abs(ord(s[i])-ord(t[i]))

            while sum > maxCost:

                sum -= abs(ord(s[start])-ord(t[start]))

                start += 1

            res = max(res, i-start+1)

        return res

方法2:同1004

class Solution:

    def equalSubstring(self, s: str, t: str, maxCost: int) -> int:

        n = len(s)

        start, sum = 00

        res = 0

        for i in range(n):

            sum += abs(ord(s[i]) - ord(t[i]))

            if sum > maxCost:

                sum -= abs(ord(s[start]) - ord(t[start]))

                start += 1

            # res = max(res, i-start+1)

        return i-start+1

 

239. 滑动窗口最大值(困难)

给定一个数组 nums,有一个大小为 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回滑动窗口中的最大值。

 

进阶:

你能在线性时间复杂度内解决此题吗?

 

示例:

输入: 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

 

提示:

1 <= nums.length <= 10^5

-10^4 <= nums[i] <= 10^4

1 <= k <= nums.length

方法:双端队列

  1. 用一个两端开口的队列,把有可能成为滑动窗口最大值的数的数组下标存入其中。
  2. 待入队元素(下标)入队:

如果待入队元素比队尾元素大(大于等于),则队尾元素(下标)出队。

  1. 当待入队元素的下标与队首元素的下标之差 >= 滑动窗口大小时,队首元素出队。

class Solution:

    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:

        deque = collections.deque()

        ans = []

        for i, num in enumerate(nums):

            if deque and i-deque[0] >= k:

                deque.popleft()

            while deque and nums[deque[-1]] < num:

                deque.pop()

            deque.append(i)

            if i >= k-1:

                ans.append(nums[deque[0]])

        return ans

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值