算法套路二十——单调栈

算法套路二十——单调栈

单调栈是一种特殊的数据结构,用于解决与元素的相对大小有关的问题。它是一个栈,但其中的元素以单调递增或单调递减的顺序排列,用于处理与相对大小有关的问题。

算法示例:下一个更大元素

给定一个数组 nums ,返回 nums 中每个元素的 下一个更大元素 。
数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1 。

单调栈(Monotonic Stack)是一种特殊的数据结构,用于解决一类与寻找下一个更大/更小元素有关的问题。它的特点是栈内的元素保持严格的单调性,可以是递增减。具体步骤如下

  1. 初始化一个空栈。
  2. 遍历数组或列表中的每个元素。
  3. 对于每个元素,执行以下操作:
    • 如果栈为空,则当前元素入栈。
    • 如果当前元素小于等于栈顶元素,当前元素入栈。
    • 如果当前元素大于栈顶元素,则说明当前元素是栈顶元素的下一个更大(更小)元素:
      • 不断地弹出栈顶元素,并记录栈顶元素的下一个更大(更小)元素为当前元素。
      • 将当前元素入栈。
  4. 继续处理步骤3,直到遍历完成。
  5. 如果栈中还有元素,说明它们没有下一个更大(更小)元素,默认为 -1 或者其他特定值。

通过使用单调栈,我们可以在线性扫描中高效地解决这类问题,而不需要套循环。这是因为栈保持了部分信息的记录,我们可以在遍历每个元素时快速找到相关的下一个更大(更小)元素或者距离。

func nextGreaterElement(nums []int) []int {
    n := len(nums)
    ans := make([]int, n)
    stack := []int{}
    for i := n - 1; i >= 0; i-- {
        num := nums[i]  
        // 注意是大于等于
        for len(stack) > 0 && num >= stack[len(stack)-1] {
            stack = stack[:len(stack)-1] // 栈顶元素出栈
        }
        if len(stack) > 0 {
            ans[i] = stack[len(stack)-1]
        } else {
            ans[i] = -1
        }
        stack = append(stack, num)
    }    
    return ans
}

算法练习一:LeetCode503. 下一个更大元素 II

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

本题可以由于是循环数组,一种方法就是将复制数组拼接在原数组后,但其实可以直接对 i 从2n-1遍历到0且在处理时对下标 i 取模即可,这样遍历到的num:=nums[i%n]即可等价于遍历了拼接数组。其余则与示例完全一样,直接使用单调栈即可。

func nextGreaterElements(nums []int) []int {
    n := len(nums)
    ans := make([]int, n)
    stack := []int{}
    // 从后往前遍历数组的两倍长度
    for i := 2*n - 1; i >= 0; i-- {
        num := nums[i % n]  
        // 注意是大于等于,因为复制了数组,所以出现相等时也需要将栈顶元素出栈
        for len(stack) > 0 && num >= stack[len(stack)-1] {
            stack = stack[:len(stack)-1] // 栈顶元素出栈
        }
        if len(stack) > 0 {
            ans[i%n] = stack[len(stack)-1]
        } else {
            ans[i%n] = -1
        }
        stack = append(stack, num)
    }    
    return ans
}

算法练习:LeetCode1019. 链表中的下一个更大节点

给定一个长度为 n 的链表 head
对于列表中的每个节点,查找下一个 更大节点 的值。也就是说,对于每个节点,找到它旁边的第一个节点的值,这个节点的值 严格大于 它的值。
返回一个整数数组 answer ,其中 answer[i] 是第 i 个节点( 从1开始 )的下一个更大的节点的值。如果第 i 个节点没有下一个更大的节点,设置 answer[i] = 0 。
在这里插入图片描述

func nextLargerNodes(head *ListNode) []int {
    type pair struct{x,i int}
    st:=[]pair{}
    ans:=[]int{}

    for cur:=head;cur!=nil;cur=cur.Next{
        ans=append(ans,0)
        for len(st) > 0 &&cur.Val>st[len(st)-1].x{
            ans[st[len(st)-1].i]=cur.Val
            st=st[:len(st)-1]
        }
        st=append(st,pair{cur.Val,len(ans)-1})

    }
    return ans
}

算法进阶一:LeetCode496. 下一个更大元素 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] 是如上所述的 下一个更大元素 。
在这里插入图片描述

哈希表+单调栈+倒序遍历

本题有一个难点就是如何找到num1在nums2中的对应位置,因此我们可以改变思路。
由于nums1是nums2的子集且nums2不重复,可以首先构建一个哈希表mp,首先利用底大顶小的单调栈预处理 nums2,倒序遍历nums2(倒序遍历可以不用记录num的下标),来记录num2中每个元素的右侧下一个更大元素,这样我们可以避免每次遍历 nums1 中的元素时都遍历其在nums2中的位置。具体步骤如下

  1. 哈希表 mp记录num2中元素的下一个更大值,单调栈 stack底大顶小,对于每个元素 num,如果 num大于栈顶的元素,那么栈顶的元素就不可能是 num 的下一个更大元素,因此从栈顶开始弹出所有小于 num 的元素,以保持栈中的元素单调递减。只有比栈顶小的元素才能入栈。
  2. 倒序遍历 nums2
    • 弹出栈中所有小于当前元素的元素。
    • 如果栈不为空,则将栈顶元素存入映射表 mp,表示当前元素的右边第一个更大元素。
    • 如果栈为空,则将 -1 存入映射表 mp,表示当前元素没有右边的更大元素。
    • 将当前元素压入栈中。
  3. 遍历 nums1,对于每个元素 num
    • 从映射表 mp 中查找 num 对应的右边第一个更大的元素,并将其存入 res 中。
func nextGreaterElement(nums1, nums2 []int) []int {
    mp := map[int]int{}  // 创建一个空的映射表,用于存储元素和其下一个更大元素的对应关系
    stack := []int{}  // 创建一个空的栈,用于辅助查找下一个更大元素
    for i := len(nums2) - 1; i >= 0; i-- {  // 从nums2的最后一个元素开始遍历
        num := nums2[i]
        for len(stack) > 0 && num >= stack[len(stack)-1] {  // 当栈不为空且当前元素大于等于栈顶元素时
            stack = stack[:len(stack)-1]  // 从栈中弹出栈顶元素,直到栈为空或当前元素小于栈顶元素
        }
        if len(stack) > 0 {
            mp[num] = stack[len(stack)-1]  // 将当前元素所对应的下一个更大元素存入映射表中
        } else {
            mp[num] = -1  // 如果栈为空,则当前元素没有下一个更大元素,将映射表的值设为-1
        }
        stack = append(stack, num)  // 将当前元素压入栈中
    }
    res := make([]int ,len(nums1))  // 创建与nums1相同长度的结果切片
    for i, num := range nums1 {
        res[i] = mp[num]  // 根据映射表找到nums1中每个元素的下一个更大元素并存入结果切片中
    }
    return res  // 返回结果切片
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Pistachiout

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值