1. 项目背景详细介绍

在算法和数据结构的学习过程中,Next Greater Element(下一个更大的元素) 问题是 单调栈应用 的经典场景之一。它的基本描述是:

给定一个数组 arr,对于数组中的每个元素,找到它右边第一个 比它大的元素;如果不存在,则返回 -1

这是对「Next Smaller Element」问题的镜像版本。

示例
输入数组:[4, 5, 2, 10, 8]
输出数组:[5, 10, 10, -1, -1]

解释:

  • 对于 4,右边第一个比它大的元素是 5

  • 对于 5,右边第一个比它大的元素是 10

  • 对于 2,右边第一个比它大的元素是 10

  • 对于 10,右边没有更大的元素,因此返回 -1

  • 对于 8,右边没有更大的元素,因此返回 -1

应用场景

  1. 股票价格:判断什么时候价格会比当前高。

  2. 气温预测:寻找未来第一个比当前温度更高的日子。

  3. 数据分析:在序列中寻找下一个上升拐点。

  4. 单调栈应用:在「直方图最大矩形」和「雨水接雨」问题中非常常见。


2. 项目需求详细介绍

  1. 输入:一个整数数组 arr

  2. 输出:一个整数数组 res,其中 res[i] 表示 arr[i] 的右边第一个比它大的元素。如果不存在,返回 -1

  3. 算法要求

    • 时间复杂度要求尽可能低(最好 O(n))。

    • 使用 辅助实现。

  4. 语言要求:使用 Java 实现。

  5. 提供完整的测试类,能运行并输出结果。


3. 相关技术详细介绍

3.1 暴力解法

  • 对于数组中的每个元素 arr[i],从右边依次遍历,找到第一个比 arr[i] 大的元素。

  • 如果找到,就作为答案;否则返回 -1

复杂度:O(n²),效率低下。

3.2 单调栈解法(优化)

使用 单调栈(Monotonic Stack) 可以把复杂度降为 O(n)。

思路

  • 从右往左遍历数组;

  • 使用一个 递减栈 来存储候选元素;

  • 对于当前元素:

    • 弹出栈顶中 小于等于当前值 的元素,因为它们不可能成为答案;

    • 如果栈为空,则说明没有更大的元素,结果为 -1

    • 如果栈顶比当前值大,则栈顶就是答案;

    • 当前元素入栈,成为后续元素的候选。


4. 实现思路详细介绍

算法流程:

  1. 创建一个结果数组 res

  2. 使用一个栈来保存右侧可能成为“下一个更大元素”的候选。

  3. 从右往左遍历:

    • 弹出栈顶元素,直到栈顶比当前元素大;

    • 如果栈为空,结果是 -1;否则栈顶就是答案;

    • 当前元素入栈。

  4. 返回结果数组。

时间复杂度:O(n),因为每个元素最多进栈和出栈一次。
空间复杂度:O(n),栈在最坏情况下可能存放所有元素。


5. 完整实现代码

import java.util.Stack;
import java.util.Arrays;

// =============================================
// Next Greater Element 算法类
// =============================================
class NextGreaterElement {

    /**
     * 计算数组中每个元素的下一个更大的元素
     * @param arr 输入数组
     * @return 结果数组
     */
    public static int[] findNextGreaterElements(int[] arr) {
        int n = arr.length;
        int[] result = new int[n];  // 存放结果
        Stack<Integer> stack = new Stack<>(); // 单调栈(递减栈)

        // 从右往左遍历
        for (int i = n - 1; i >= 0; i--) {
            int current = arr[i];

            // 弹出比当前元素小或等于的值
            while (!stack.isEmpty() && stack.peek() <= current) {
                stack.pop();
            }

            // 如果栈为空,说明右边没有更大的元素
            result[i] = stack.isEmpty() ? -1 : stack.peek();

            // 当前元素入栈
            stack.push(current);
        }

        return result;
    }
}

// =============================================
// 测试类 Main
// =============================================
public class Main {
    public static void main(String[] args) {
        int[] arr = {4, 5, 2, 10, 8};
        System.out.println("输入数组: " + Arrays.toString(arr));

        int[] result = NextGreaterElement.findNextGreaterElements(arr);

        System.out.println("下一个更大元素数组: " + Arrays.toString(result));
    }
}

6. 代码详细解读

  1. 核心方法 findNextGreaterElements

    • 输入:一个整型数组 arr

    • 输出:对应的结果数组 result

    • 使用 单调递减栈 存储候选元素;

    • 从右往左遍历数组,每次保证栈顶始终是“比当前值大的候选元素”;

    • 如果栈空,说明没有更大的元素,返回 -1

    • 否则,栈顶即为下一个更大的元素。

  2. 栈的使用

    • 栈中始终保持一个递减序列;

    • 保证了 栈顶就是离当前最近的更大元素

    • 每个元素最多进出栈一次,总复杂度 O(n)。

  3. Main 测试类

    • 定义数组 [4, 5, 2, 10, 8]

    • 调用 findNextGreaterElements 方法;

    • 输出 [5, 10, 10, -1, -1]


7. 项目详细总结

  • 暴力解法 时间复杂度 O(n²),不适合大规模数据;

  • 单调栈解法 时间复杂度 O(n),是最优解;

  • 核心思想:通过栈维护一个候选集合,保证栈顶就是下一个更大的元素;

  • 典型应用场景

    • 股票分析 → 找下一次价格上涨;

    • 气温预测 → 找下一次升温;

    • 数据处理 → 寻找趋势拐点;

    • 算法题 → 「直方图最大矩形」、「每日温度」等题目都基于此思路。


8. 项目常见问题及解答

Q1:为什么要从右往左遍历?
答:因为我们要找的是“右边的元素”,从右往左遍历能保证栈中保存的都是右侧的候选值。

Q2:为什么栈中要弹出小于等于当前值的元素?
答:因为这些元素不可能成为当前值的“下一个更大元素”。

Q3:能否从左往右遍历?
答:可以,但实现逻辑会更复杂,一般建议从右往左。

Q4:如果要找“下一个更小元素”,如何修改?
答:只需要调整条件,把弹出逻辑改为“弹出大于等于当前值的元素”。


9. 扩展方向与性能优化

  1. 泛型化实现

    • 可以扩展支持任意实现 Comparable 接口的对象,例如字符串、浮点数等。

  2. 环形数组问题

    • 在 LeetCode 上常见变体是 循环数组,即需要考虑数组首尾相接的情况,可以通过遍历两倍数组来解决。

  3. 双向扩展

    • 不仅可以找右边的下一个更大元素,还可以找左边的下一个更大元素。

  4. 空间优化

    • 如果允许覆盖原数组,可以直接在 arr 中写结果,从而减少额外空间。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐