(Java解题)单调栈:最大乘积问题 | 豆包MarsCode AI刷题

49 阅读3分钟

题目:最大乘积问题

问题描述

小R最近遇到了一个数组问题。他有一个包含 NN 个元素的数组,记作 a1,a2,...,aNa_1, a_2, ..., a_N。为了分析这个数组的特性,小R定义了两个函数 L(i)L(i)R(i)R(i),并希望通过这两个函数来找到一些有趣的结论。

  • L(i)L(i) 是满足以下条件的最大的 jj 值:

    • j<ij < i
    • a[j]>a[i]a[j] > a[i]
    • 如果找不到这样的 jj,那么 L(i)=0L(i) = 0;如果有多个满足条件的 jj,选择离 ii 最近的那个。
  • R(i)R(i) 是满足以下条件的最小的 kk 值:

    • k>ik > i
    • a[k]>a[i]a[k] > a[i]
    • 如果找不到这样的 kk,那么 R(i)=0R(i) = 0;如果有多个满足条件的 kk,选择离 ii 最近的那个。

最终,小R定义 MAX(i)=L(i)R(i)MAX(i) = L(i) * R(i),他想知道在 1iN1 \leq i \leq N 的范围内,MAX(i)MAX(i) 的最大值是多少。

测试样例

样例1:

输入:n = 5, array = [5, 4, 3, 4, 5]
输出:8

样例2:

输入:n = 6, array = [2, 1, 4, 3, 6, 5]
输出:15

样例3:

输入:n = 7, array = [1, 2, 3, 4, 5, 6, 7]
输出:0


问题理解

  • 给定一个包含 N 个元素的数组 array

  • 定义两个函数 L(i) 和 R(i)

    • L(i) 是满足 j < i 且 array[j] > array[i] 的最大的 j 值,如果找不到这样的 j,则 L(i) = 0
    • R(i) 是满足 k > i 且 array[k] > array[i] 的最小的 k 值,如果找不到这样的 k,则 R(i) = 0
  • 定义 MAX(i) = L(i) * R(i),目标是找到 MAX(i) 的最大值。

数据结构选择

  • 数组:用于存储输入的数组元素。
  • :用于高效地找到 L(i) 和 R(i)。栈可以帮助我们在遍历数组时,快速找到左边或右边第一个比当前元素大的元素。

算法步骤

  1. 初始化

    • 创建两个数组 L 和 R,分别用于存储每个元素的 L(i) 和 R(i)
    • 创建一个栈,用于辅助计算 L(i) 和 R(i)
  2. 计算 L(i)

    • 从左到右遍历数组,对于每个元素 array[i]

      • 如果栈不为空且栈顶元素对应的数组值小于等于 array[i],则弹出栈顶元素。
      • 如果栈为空,则 L[i] = 0;否则,L[i] = 栈顶元素的索引 + 1
      • 将当前元素的索引入栈。
  3. 计算 R(i)

    • 从右到左遍历数组,对于每个元素 array[i]

      • 如果栈不为空且栈顶元素对应的数组值小于等于 array[i],则弹出栈顶元素。
      • 如果栈为空,则 R[i] = 0;否则,R[i] = 栈顶元素的索引 + 1
      • 将当前元素的索引入栈。
  4. 计算 MAX(i)

    • 遍历数组,计算 MAX(i) = L[i] * R[i],并记录最大值。

代码实现

import java.util.Stack;

public class Main {
    public static int solution(int n, int[] array) {
        // 初始化 L 和 R 数组
        int[] L = new int[n];
        int[] R = new int[n];
        
        // 使用栈来计算 L(i)
        Stack<Integer> stack = new Stack<>();
        for (int i = 0; i < n; i++) {
            while (!stack.isEmpty() && array[stack.peek()] <= array[i]) {
                stack.pop();
            }
            L[i] = stack.isEmpty() ? 0 : stack.peek() + 1;
            stack.push(i);
        }
        
        // 清空栈,用于计算 R(i)
        stack.clear();
    
        for (int i = n - 1; i >= 0; i--) {
            while (!stack.isEmpty() && array[stack.peek()] <= array[i]) {
                stack.pop();
            }
            R[i] = stack.isEmpty() ? 0 : stack.peek() + 1;
            stack.push(i);
        }
        
        // 计算 MAX(i) 并找出最大值
        int maxProduct = 0;
        for (int i = 0; i < n; i++) {
            maxProduct = Math.max(maxProduct, L[i] * R[i]);
        }
        
        return maxProduct;
    }

    public static void main(String[] args) {
        // Add your test cases here
        System.out.println(solution(5, new int[]{5, 4, 3, 4, 5}) == 8);
    }
}

关键点

  1. 单调栈的维护:在计算 L(i) 和 R(i) 时,栈中始终保持单调递减的顺序。
  2. 索引的处理:栈中存储的是数组元素的索引,而不是元素值本身。
  3. 边界条件:当栈为空时,表示没有找到符合条件的元素,此时 L(i) 或 R(i) 为 0。

复杂度分析

  • 时间复杂度O(N),每个元素最多入栈和出栈一次。
  • 空间复杂度O(N),用于存储 L 和 R 数组以及栈。

做题心得

写代码前要理好思路:

只有比栈顶元素小的元素才能直接进栈,否则需要先将栈中比当前元素小的元素出栈,再将当前元素入栈。

这样就保证了栈中保留的都是比当前入栈元素大的值,并且从栈顶到栈底的元素值是单调递增的。