AI刷题 122. 最大乘积问题 题解 | 豆包MarsCode AI刷题

51 阅读5分钟

题目描述

小R最近遇到了一个数组问题。他有一个包含 NN 个元素的数组,记作 a1,a2,...,aNa1​,a2​,...,aN​。为了分析这个数组的特性,小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),他想知道在 1≤i≤N1≤i≤N 的范围内,MAX(i)MAX(i) 的最大值是多少。

题目背景与目标

给定一个长度为N的数组array,我们定义了两个函数L(i)R(i),分别表示对于数组array中第i个元素,满足某些条件的最近位置。具体来说:

  • L(i) 是数组中所有满足a[j] > a[i]j < i的元素中,离i最近的索引。如果没有这样的元素,则L(i) = 0
  • R(i) 是数组中所有满足a[k] > a[i]k > i的元素中,离i最近的索引。如果没有这样的元素,则R(i) = 0

我们需要计算出每个iMAX(i),其中:

scss
复制代码
MAX(i) = L(i) * R(i)

目标是找到所有MAX(i)中的最大值。

题目理解与要点

我们有以下关键点需要理解:

  1. L(i) 的定义是找到比a[i]大的元素,并且这个元素的索引ji之前。如果找不到这样的元素,返回0。
  2. R(i) 的定义是找到比a[i]大的元素,并且这个元素的索引ki之后。如果找不到这样的元素,返回0。
  3. 最终的目标是计算L(i) * R(i)的最大值,1 <= i <= N

解题思路

要解决这个问题,我们可以通过以下步骤进行:

  1. 初始化数组

    • 我们需要为每个i计算L(i)R(i),因此,我们需要初始化两个数组LR,它们的大小都是N,并且初始值设为0。
  2. 计算L(i)

    • 对于每个i,我们需要从左往右遍历,寻找第一个a[j] > a[i]j < i的元素。
    • 由于题目要求选择离i最近的满足条件的j,因此一旦找到第一个满足条件的j,我们就可以停止搜索。
  3. 计算R(i)

    • 对于每个i,我们需要从右往左遍历,寻找第一个a[k] > a[i]k > i的元素。
    • 同样地,找到第一个满足条件的k后,我们可以停止搜索。
  4. 计算MAX(i)

    • 一旦得到了L(i)R(i),我们可以计算MAX(i) = L(i) * R(i),并更新最大值。
  5. 时间复杂度

    • 对于每个i,我们分别要遍历左侧和右侧的元素,所以整体时间复杂度是O(N^2),其中N是数组的大小。这个复杂度对于N较小的情况是可接受的,但如果N非常大(如上千或上万),需要考虑优化。

优化的空间

当前的解法在计算L(i)R(i)时,使用了两次嵌套循环,这使得整体时间复杂度为O(N^2),对于较大的输入,这种解法可能会变得效率低下。为了提高效率,我们可以考虑使用单调栈来优化。

优化方案:单调栈

使用单调栈可以在O(N)时间内解决L(i)R(i)的计算问题。通过栈,我们可以保证每次弹出的元素都是符合条件的最近的元素。

  1. 计算L(i)

    • 我们使用一个栈来维护一个单调递减的序列。对于每个i,如果栈顶元素小于等于a[i],则将栈顶元素弹出,直到栈顶元素大于a[i]。此时栈顶元素即为L(i)
  2. 计算R(i)

    • 类似地,我们可以使用另一个栈来计算R(i),但这次是从右往左遍历数组。栈内保持单调递减的顺序,找到第一个大于a[i]的元素。

通过这种方式,我们可以将计算L(i)R(i)的时间复杂度从O(N^2)优化到O(N)

最终的代码实现

java
复制代码
public class Main {
    public static int solution(int n, int[] array) {
        int[] L = new int[n];
        int[] R = new int[n];
        
        // 计算 L(i) 使用单调栈
        Stack<Integer> stackL = new Stack<>();
        for (int i = 0; i < n; i++) {
            while (!stackL.isEmpty() && array[stackL.peek()] <= array[i]) {
                stackL.pop();
            }
            L[i] = (stackL.isEmpty()) ? 0 : stackL.peek() + 1; // 加1是因为要返回1-based索引
            stackL.push(i);
        }

        // 计算 R(i) 使用单调栈
        Stack<Integer> stackR = new Stack<>();
        for (int i = n - 1; i >= 0; i--) {
            while (!stackR.isEmpty() && array[stackR.peek()] <= array[i]) {
                stackR.pop();
            }
            R[i] = (stackR.isEmpty()) ? 0 : stackR.peek() + 1; // 加1是因为要返回1-based索引
            stackR.push(i);
        }

        // 计算最大值
        int maxProduct = 0;
        for (int i = 0; i < n; i++) {
            int product = L[i] * R[i];
            maxProduct = Math.max(maxProduct, product);
        }

        return maxProduct;
    }

    public static void main(String[] args) {
        // 测试用例
        System.out.println(solution(5, new int[]{5, 4, 3, 4, 5}) == 8);
        System.out.println(solution(6, new int[]{2, 1, 4, 3, 6, 5}) == 15);
        System.out.println(solution(7, new int[]{1, 2, 3, 4, 5, 6, 7}) == 0);
    }
}

总结

通过优化的单调栈方法,我们能够将原本的O(N^2)算法降至O(N),极大提高了效率。这种方法通过栈维护单调性,确保了我们能够在每次遍历时快速找到满足条件的L(i)R(i),从而有效地解决了最大乘积问题。