news 2026/9/10 19:53:22

LeetCode 496. Next Greater Element I 题解:Go 单调栈与哈希表实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode 496. Next Greater Element I 题解:Go 单调栈与哈希表实战

LeetCode 496. Next Greater Element I 题解:Go 单调栈与哈希表实战

【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go

导读

本文以 LeetCode 496. Next Greater Element I 为切入点,讲解"下一个更大元素"(Next Greater Element, NGE)这一高频算法题型的定义、约束与两种经典解法:哈希表辅助的暴力扫描,以及单调栈(Monotonic Stack)优化。结合 LeetCode-Go 仓库中的 官方实现 与 单元测试,读者将掌握 NGE 问题的建模方法、Go 语言实现细节与复杂度权衡,并能顺势理解环形场景下的进阶变体(LeetCode 503)。


一、问题定义:什么是"下一个更大元素"

1.1 题目原文

题目给定两个数组nums1nums2,其中nums1中的元素是nums2的子集,且两个数组内均不含重复元素。需要为nums1中的每一个元素x,在nums2中对应位置向右寻找第一个比x大的数,即 "Next Greater Number";若不存在,则输出-1

1.2 关键术语

  • 下一个更大元素(NGE):元素xnums2右侧(索引比x所在位置大)第一个大于x的数值,而非最大值、也非最右侧的值。
  • 子集映射nums1nums2的子集,答案的查找位置以nums2为准,输出顺序与nums1保持一致。

1.3 示例一

Input: nums1 = [4,1,2], nums2 = [1,3,4,2]. Output: [-1,3,-1] Explanation: For number 4 in the first array, you cannot find the next greater number for it in the second array, so output -1. For number 1 in the first array, the next greater number for it in the second array is 3. For number 2 in the first array, there is no next greater number for it in the second array, so output -1.

逐步验证:

  • 4nums2中位于索引 2,其右侧只有22 < 4,故输出-1
  • 1nums2中位于索引 0,向右第一个比1大的数是3(索引 1),输出3
  • 2nums2中位于索引 3,右侧无元素,输出-1

1.4 示例二

Input: nums1 = [2,4], nums2 = [1,2,3,4]. Output: [3,-1] Explanation: For number 2 in the first array, the next greater number for it in the second array is 3. For number 4 in the first array, there is no next greater number for it in the second array, so output -1.
  • 2位于nums2索引 1,右侧第一个大于2的是3
  • 4位于末尾,右侧无更大元素,输出-1

1.5 约束条件(Note)

  • nums1nums2中的所有元素均唯一(无重复值,这一性质使哈希映射成为可能);
  • 两个数组的长度均不超过 1000(数据规模中等,暴力法在理论上是可行的,但单调栈更具推广价值)。

二、题目大意与题型定位

2.1 中文题意

题目给出 2 个数组 A 和 B,针对 A 中的每个元素,要求在 B 数组中找出比该元素大的数,且 B 中元素之间的顺序保持不变:即从该元素在 B 中的位置起向右扫描,找到第一个大于它的值就输出;找不到就输出-1

2.2 题型归属

从仓库目录结构看,本题位于 leetcode/0496.Next-Greater-Element-I,与 0503.Next-Greater-Element-II(环形数组 + 单调栈)构成同一题型家族。"下一个更大元素"是单调栈最经典的入门场景,掌握它可以平滑迁移到柱状图最大矩形、每日温度、滑动窗口极值等大量栈类问题。


三、解法一:哈希表定位 + 线性扫描(仓库官方实现)

LeetCode-Go 仓库的 496 号实现 采用了"哈希表记录下标 + 向右扫描"的思路,完整代码如下:

package leetcode func nextGreaterElement(nums1 []int, nums2 []int) []int { if len(nums1) == 0 || len(nums2) == 0 { return []int{} } res, reocrd := []int{}, map[int]int{} for i, v := range nums2 { reocrd[v] = i } for i := 0; i < len(nums1); i++ { flag := false for j := reocrd[nums1[i]]; j < len(nums2); j++ { if nums2[j] > nums1[i] { res = append(res, nums2[j]) flag = true break } } if flag == false { res = append(res, -1) } } return res }

3.1 实现要点逐行拆解

  1. 空值兜底:若nums1nums2为空,直接返回空切片[]int{},避免后续下标越界;
  2. 建立值 → 下标的哈希表:遍历nums2,用map[int]int记录每个值在nums2中的索引(变量名reocrd为 "record" 的笔误,不影响逻辑);
  3. 遍历nums1查找 NGE:对每个元素,先从哈希表取出其在nums2中的起始下标,从该位置向右扫描:
    • 一旦发现nums2[j] > nums1[i],即为"下一个更大元素",记录后break
    • flag标记是否找到;若扫描完仍未找到,追加-1

3.2 复杂度分析

指标复杂度说明
时间复杂度O(n × m)建表 O(m),最坏情况下每个nums1元素都要向右扫描 O(m)
空间复杂度O(m)哈希表存储nums2全部元素的下标

由于nums1nums2长度均不超过 1000,最坏约 10⁶ 次比较,在 LeetCode 约束下完全可行,这也是该解法能被仓库收录为"简单题直接实现"的原因。


四、解法二:单调栈 + 哈希表(推荐推广版)

暴力法的缺点在于:对nums1的每个元素都重复向右扫描,信息未被复用。单调栈可以一次性为nums2中所有元素求出 NGE,再借助哈希表 O(1) 查询,将整体复杂度优化到 O(n + m)。

4.1 算法流程

  1. 初始化:一个空栈stack(维护"尚未找到 NGE"的递减序列),一个哈希表nextGreater
  2. 从右向左遍历nums2(或从左向右,两种方向等价):
    • 当栈非空且nums2[i] >= stack 顶时,不断出栈——栈顶这些更小(或相等)的元素不可能是nums2[i]的 NGE 候选;
    • 出栈结束后,若栈非空,则栈顶即nums2[i]的下一个更大元素,记入nextGreater;若栈空,则记为-1
    • nums2[i]入栈;
  3. 查询输出:遍历nums1,从nextGreater中取出每个元素的答案。

核心不变式:栈内元素自底向上严格递减,栈顶永远是最接近当前位置、且尚未匹配到 NGE 的元素。

4.2 Go 参考实现

func nextGreaterElement(nums1 []int, nums2 []int) []int { stack := []int{} nextGreater := make(map[int]int, len(nums2)) // 从右向左扫描 nums2,维护单调递减栈 for i := len(nums2) - 1; i >= 0; i-- { for len(stack) > 0 && stack[len(stack)-1] < nums2[i] { stack = stack[:len(stack)-1] // 弹出比当前值小的元素 } if len(stack) == 0 { nextGreater[nums2[i]] = -1 } else { nextGreater[nums2[i]] = stack[len(stack)-1] } stack = append(stack, nums2[i]) } res := make([]int, len(nums1)) for i, v := range nums1 { res[i] = nextGreater[v] } return res }

4.3 复杂度对比

指标暴力 + 哈希单调栈 + 哈希
时间复杂度O(n × m)O(n + m),每个元素至多入栈、出栈一次
空间复杂度O(m)O(m)(栈 + 哈希表)

单调栈将多次重复扫描合并为一次线性遍历,且不依赖"长度不超过 1000"的约束,可以平滑应对大规模数据,是面试与工程中的首选写法。


五、单调栈的可视化推演

nums2 = [1, 3, 4, 2]nums1 = [4, 1, 2]为例,从右向左执行单调栈:

步骤当前元素栈(底→顶)出栈操作NGE 结果
12[]2 → -1,入栈[2]
24[2]弹出2(2 < 4)4 → -1,入栈[4]
33[4]无(4 > 3)3 → 4,入栈[4, 3]
41[4, 3]无(3 > 1)1 → 3,入栈[4, 3, 1]

最终nextGreater = {2: -1, 4: -1, 3: 4, 1: 3},代入nums1得答案[-1, 3, -1],与题目示例一完全一致。可以看出,栈顶元素就是"当前元素右侧最近且比它大"的候选,出栈动作天然保证了这一点。


六、单元测试验证:仓库测试用例剖析

仓库为本题配套了完整的 单元测试,采用"参数 + 期望答案"的结构化用例组织方式:

package leetcode import ( "fmt" "testing" ) type question496 struct { para496 ans496 } // para 是参数 // one 代表第一个参数 type para496 struct { one []int another []int } // ans 是答案 // one 代表第一个答案 type ans496 struct { one []int } func Test_Problem496(t *testing.T) { qs := []question496{ { para496{[]int{4, 1, 2}, []int{1, 3, 4, 2}}, ans496{[]int{-1, 3, -1}}, }, { para496{[]int{2, 4}, []int{1, 2, 3, 4}}, ans496{[]int{3, -1}}, }, { para496{[]int{}, []int{}}, ans496{[]int{}}, }, // 如需多个测试,可以复制上方元素。 } fmt.Printf("------------------------Leetcode Problem 496------------------------\n") for _, q := range qs { _, p := q.ans496, q.para496 fmt.Printf("【input】:%v 【output】:%v\n", p, nextGreaterElement(p.one, p.another)) } fmt.Printf("\n\n\n") }

6.1 测试设计要点

  • 覆盖题目两个官方示例:验证[-1, 3, -1][3, -1]两组基准答案;
  • 覆盖空输入边界nums1nums2均为空时返回[]int{},与 实现源码 中的空值兜底分支一一对应;
  • 结构化的 para/ans 组合:通过匿名结构体嵌套,把"输入参数"与"期望答案"绑定在一起,新增用例只需复制一条question496字面量,扩展成本极低。

这种para/ans测试模式在整个仓库中被大量复用,是 LeetCode-Go 一以贯之的工程化测试约定。运行方式遵循仓库标准流程:

go test -v ./leetcode/0496.Next-Greater-Element-I/...

(需在仓库根目录下执行,依赖 go.mod 中声明的 modulegithub.com/halfrost/LeetCode-Go。)


七、延伸进阶:环形变体 LeetCode 503

掌握了 496 的单调栈写法后,可以无缝迁移到同仓库的 503. Next-Greater-Element-II(环形数组,nums可视为首尾相接)。该题在 496 基础上仅多了一步:将数组"虚拟翻倍",即遍历2 * len(nums)次,用i % len(nums)取模访问元素

// 解法一 单调栈 func nextGreaterElements(nums []int) []int { res := make([]int, 0) indexes := make([]int, 0) for i := 0; i < len(nums); i++ { res = append(res, -1) } for i := 0; i < len(nums)*2; i++ { num := nums[i%len(nums)] for len(indexes) > 0 && nums[indexes[len(indexes)-1]] < num { index := indexes[len(indexes)-1] res[index] = num indexes = indexes[:len(indexes)-1] } indexes = append(indexes, i%len(nums)) } return res }

注意这里栈中存的是下标而非数值,从而能直接回填结果数组resi < len(nums)*2的取模遍历等价于在逻辑上把nums复制一份拼在尾部,保证了"环上每个元素都能在自身右侧(含绕环)找到 NGE"。该文件还同时给出了不使用单调栈的朴素解法nextGreaterElements1,两相对照,可以直观感受到单调栈对复杂度的收益。


八、总结

维度关键结论
问题本质为子集数组中每个元素,在母数组中向右找第一个更大的值,找不到输出-1
前置性质两数组元素唯一(哈希映射的前提),长度 ≤ 1000(暴力可行)
仓库官方实现哈希表 + 向右扫描,O(n × m) 时间、O(m) 空间
推荐进阶实现单调栈一次遍历求出全部 NGE,O(n + m) 时间、O(m) 空间
验证手段结构化 para/ans 单元测试,覆盖双示例与空输入
题型延伸同仓库 503 环形更大元素 基于取模遍历即可复用单调栈

"下一个更大元素"是单调栈思维的第一块跳板:它把"为每个元素重复扫描"的冗余计算,压缩为"栈内元素严格单调、每个元素仅进出栈一次"的线性过程。建议读者对照 496 README 题解 亲手实现一遍暴力版与单调栈版,再用仓库测试用例校验,随后挑战 503 环形变体,即可完整掌握这一经典题型。

【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 19:53:20

SpringBoot+Vue城市公交调度系统设计与实现:从排班到实时监控

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 19:52:30

Python实现Linux抓包工具:从原理到实战

1. 为什么需要自己写抓包工具&#xff1f;在Linux环境下&#xff0c;虽然已经有Wireshark、tcpdump这样的专业抓包工具&#xff0c;但自己动手实现一个简易版本依然很有价值。我最初产生这个想法&#xff0c;是因为在一次服务器排障中遇到了特殊需求——需要实时过滤特定进程产…

作者头像 李华
网站建设 2026/9/10 19:48:46

Flutter跨端实战:为OpenHarmony打造数独生成器

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华