Leetcode 42
1 题目
503. 下一个更大元素 II
给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。
数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1 。
示例 1:
输入: nums = [1,2,1] 输出: [2,-1,2] 解释: 第一个 1 的下一个更大的数是 2; 数字 2 找不到下一个更大的数; 第二个 1 的下一个最大的数需要循环搜索,结果也是 2。
示例 2:
输入: nums = [1,2,3,4,3] 输出: [2,3,4,-1,4]
提示:
- 1 <= nums.length <= 104
- -109 <= nums[i] <= 109
2 代码实现
int* nextGreaterElements(int* nums, int numsSize, int* returnSize) {*returnSize = numsSize;if (numsSize == 0) {return NULL;}int* ret = malloc(sizeof(int) * numsSize);memset(ret, -1, sizeof(int) * numsSize);int stk[numsSize * 2 - 1], top = 0;for (int i = 0; i < numsSize * 2 - 1; i++) {while (top > 0 && nums[stk[top - 1]] < nums[i % numsSize]) {ret[stk[top - 1]] = nums[i % numsSize];top--;}stk[top++] = i % numsSize;}return ret;
}
和之前的 Leetcode 40-CSDN博客 的区别就是要有循环数组,原来定义的最后一个元素会循环回去继续查找。并且注意返回的不是索引差而是元素。
半途而废的代码
我发现我写不下去了,因为其实循环数组的处理我没弄清楚,一般是要取模的,不会操作。
/*** Note: The returned array must be malloced, assume caller calls free().*/
int* nextGreaterElements(int* nums, int numsSize, int* returnSize) {//创建存储结果的数组* returnSize = numsSize ;int * answer = (int *)malloc (sizeof(int) * numsSize);for(int i = 0 ; i < numsSize ; i ++ ){answer[i] = -1 ;}//初始化单调栈int *stack = (int * ) malloc (sizeof (int) * numsSize);int top = -1 ;for(int i = 0 ; i < numsSize ; i++){int cur = nums[i];while(top != -1 ){int stack_top_idx = stack [top];int stack_top_num = nums[stack_top_idx];if(cur > stack_top_num){answer[stack_top_idx] = }}top++;stack[top] = i ;}}一、核心疑惑:循环数组如何用 “取模” 处理?
循环数组的本质是 “让数组的末尾元素,能接着从开头元素继续查找”。直接复制一份数组拼在后面(如 [1,2,1] 变成 [1,2,1,1,2])可以模拟循环,但会浪费空间。取模操作能在不额外复制数组的前提下,实现 “循环遍历” 的效果。
- 取模的原理:对于长度为 n的数组,当遍历索引i超过n-1时,i % n会自动映射回数组的合法索引。- 例:n=3,i=3时,3%3=0(对应数组第 1 个元素);i=4时,4%3=1(对应数组第 2 个元素)。
 
- 例:
- 遍历范围为何是 2n-1?:要确保每个元素都能找到 “循环后的下一个更大元素”,最多只需遍历2n-1个元素。- 例:n=3,遍历到i=4(即2*3-1=5?不,代码中是i < 2n-1,实际遍历0-4共 5 次),能覆盖最后一个元素(i=2)循环查找开头两个元素(i=3、i=4)的场景。
 
- 例:
二、你的代码缺什么?补全逻辑 + 注释
你的代码已经初始化了结果数组、单调栈,但在 “弹出栈顶元素并赋值” 和 “循环遍历” 两步有缺失。下面是补全后的完整代码,关键步骤已标注注释:
/*** Note: The returned array must be malloced, assume caller calls free().*/
int* nextGreaterElements(int* nums, int numsSize, int* returnSize) {// 1. 初始化返回结果:长度与原数组一致,默认值-1(找不到更大元素时用)*returnSize = numsSize;int *answer = (int *)malloc(sizeof(int) * numsSize);for (int i = 0; i < numsSize; i++) {answer[i] = -1;}// 2. 初始化单调栈:存储的是“数组元素的索引”(而非值),方便后续更新结果int *stack = (int *)malloc(sizeof(int) * numsSize);int top = -1; // top=-1 表示栈为空// 3. 循环遍历:范围是 0 ~ 2n-2(共 2n-1 次),用取模模拟循环数组for (int i = 0; i < 2 * numsSize - 1; i++) {// 当前遍历的元素值(取模得到合法索引)int current_val = nums[i % numsSize];// 4. 单调栈核心逻辑:栈非空且当前值 > 栈顶索引对应的值// 说明当前值是栈顶元素的“下一个更大元素”,更新结果后弹出栈顶while (top != -1 && current_val > nums[stack[top]]) {// 栈顶索引对应的结果 = 当前值answer[stack[top]] = current_val;// 弹出栈顶(处理完一个元素,继续检查新的栈顶)top--;}// 5. 压入当前索引:注意只压入“原数组的索引”(0~numsSize-1)// 避免重复压入同一元素的索引(如 i=3 对应 i%3=0,无需再压入)if (i < numsSize) {top++;stack[top] = i % numsSize;}}// 释放栈的内存(避免内存泄漏)free(stack);return answer;
}
三、关键步骤拆解:为什么这么写?
1. 单调栈存 “索引” 而非 “值”
- 因为我们需要知道 “哪个位置的元素” 找到了下一个更大元素,才能更新 answer数组对应位置的值。如果存值,会丢失原位置信息。
2. 为什么 “只在 i < numsSize 时压栈”?
- 当 i >= numsSize时,i%numsSize会重复出现原数组的索引(如i=3对应0)。此时压栈会导致同一索引被多次压入,造成重复处理和错误。
- 只需在第一次遍历原数组(i < numsSize)时压入所有索引,后续遍历(i >= numsSize)仅负责 “检查并更新结果”。
3. 举个例子:用 nums = [1,2,1] 验证
- 遍历 i=0(值 1):栈空,压入索引 0 → 栈:[0]
- 遍历 i=1(值 2):2>1,弹出索引 0,answer[0]=2;栈空,压入索引 1 → 栈:[1]
- 遍历 i=2(值 1):1<2,压入索引 2 → 栈:[1,2]
- 遍历 i=3(i%3=0,值 1):1<2,不操作 → 栈不变
- 遍历 i=4(i%3=1,值 2):2>1,弹出索引 2,answer[2]=2;2 不大于 2(栈顶索引 1 的值),停止 → 栈:[1]
- 最终 answer = [2,-1,2],与示例一致。
四、你的代码 vs 参考代码:核心差异
| 对比项 | 你的代码 | 完整代码 | 
|---|---|---|
| 遍历范围 | 仅遍历原数组( i < numsSize) | 遍历 2n-1次(i < 2n-1) | 
| 取模操作 | 未使用 | 用 i%numsSize模拟循环 | 
| 栈顶元素赋值 | 未补全 answer[stack_top_idx] = ? | 赋值为 current_val后弹出栈顶 | 
| 重复压栈问题 | 无(但遍历范围不足) | 仅 i < numsSize时压栈 | 
3 单调栈解决循环数组下一个更大元素问题 - 通用模板
一、模板核心思路
- 循环数组模拟:用 “遍历范围扩展到 2n-1 次 + 取模操作”,替代额外复制数组,实现循环查找效果。
- 单调栈作用:存储数组索引(而非值),维护一个 “单调递减” 的栈结构,快速找到 “下一个更大元素”。
- 结果初始化:默认值设为 -1,避免后续漏处理 “找不到更大元素” 的情况。
二、可直接复用的代码模板
/*** 功能:求解循环数组中每个元素的下一个更大元素* 参数:*   nums:输入的循环数组*   numsSize:数组长度*   returnSize:输出数组的长度(需赋值为 numsSize)* 返回值:动态分配的结果数组(需调用者手动 free)*/
int* nextGreaterElementsTemplate(int* nums, int numsSize, int* returnSize) {// 1. 初始化返回结果:长度与原数组一致,默认值-1*returnSize = numsSize;int *result = (int *)malloc(sizeof(int) * numsSize);if (result == NULL) { // 避免内存分配失败return NULL;}for (int i = 0; i < numsSize; i++) {result[i] = -1;}// 2. 初始化单调栈:存储数组索引,top=-1 表示栈空int *stack = (int *)malloc(sizeof(int) * numsSize); // 栈最大容量为 numsSize(无需更大)if (stack == NULL) {free(result); // 内存分配失败时,释放已分配的 resultreturn NULL;}int top = -1;// 3. 核心循环:遍历 2n-1 次,用取模模拟循环数组for (int i = 0; i < 2 * numsSize - 1; i++) {// 当前遍历的元素索引(取模得到合法索引)和值int currentIdx = i % numsSize;int currentVal = nums[currentIdx];// 4. 单调栈逻辑:弹出栈顶并更新结果(当前值是栈顶元素的下一个更大元素)// 栈非空 + 当前值 > 栈顶索引对应的值 → 满足条件,持续弹出while (top != -1 && currentVal > nums[stack[top]]) {result[stack[top]] = currentVal; // 栈顶索引的结果 = 当前值top--; // 弹出栈顶,继续检查新栈顶}// 5. 压栈:仅在第一次遍历原数组时压入(避免重复压入同一索引)if (i < numsSize) {top++;stack[top] = currentIdx;}}// 6. 释放临时内存(栈),避免内存泄漏free(stack);return result;
}
三、模板使用说明
1. 直接套用场景
- 题目要求 “循环数组”+“下一个更大元素”,且返回 “元素值”(非索引差、非距离)。
- 示例:LeetCode 503. 下一个更大元素 II(直接替换函数名即可使用)。
2. 灵活修改场景
若题目需求有细微变化,只需修改以下部分:
- 改 “下一个更小元素”:将 currentVal > nums[stack[top]]改为currentVal < nums[stack[top]]。
- 改返回 “索引差”:将 result[stack[top]] = currentVal改为result[stack[top]] = currentIdx - stack[top]。
- 非循环数组:将遍历范围 2 * numsSize - 1改为numsSize,并删除currentIdx = i % numsSize(直接用i即可)。
3. 注意事项
- 内存管理:模板中已处理 stack的释放,但调用函数后,需手动free返回的result数组。
- 边界条件: - 数组长度为 1 时:结果必为 [-1](循环后无其他元素)。
- 元素全递减(如 [5,4,3,2,1]):结果全为[-1]。
 
- 数组长度为 1 时:结果必为 
四、模板验证(以 LeetCode 503 示例 1 为例)
输入:nums = [1,2,1],numsSize = 3
- 遍历 i=0(currentIdx=0,val=1):栈空,压入 0 → 栈:[0]
- 遍历 i=1(currentIdx=1,val=2):2>1,弹出 0,result[0]=2;压入 1 → 栈:[1]
- 遍历 i=2(currentIdx=2,val=1):1<2,压入 2 → 栈:[1,2]
- 遍历 i=3(currentIdx=0,val=1):1<2,不操作 → 栈不变
- 遍历 i=4(currentIdx=1,val=2):2>1,弹出 2,result[2]=2;2 不大于 2,停止 → 栈:[1]
- 最终结果:[2,-1,2],与示例一致。
