题目描述
小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。
我们需要计算出每个i的MAX(i),其中:
scss
复制代码
MAX(i) = L(i) * R(i)
目标是找到所有MAX(i)中的最大值。
题目理解与要点
我们有以下关键点需要理解:
- L(i) 的定义是找到比
a[i]大的元素,并且这个元素的索引j在i之前。如果找不到这样的元素,返回0。 - R(i) 的定义是找到比
a[i]大的元素,并且这个元素的索引k在i之后。如果找不到这样的元素,返回0。 - 最终的目标是计算
L(i) * R(i)的最大值,1 <= i <= N。
解题思路
要解决这个问题,我们可以通过以下步骤进行:
-
初始化数组:
- 我们需要为每个
i计算L(i)和R(i),因此,我们需要初始化两个数组L和R,它们的大小都是N,并且初始值设为0。
- 我们需要为每个
-
计算L(i) :
- 对于每个
i,我们需要从左往右遍历,寻找第一个a[j] > a[i]且j < i的元素。 - 由于题目要求选择离
i最近的满足条件的j,因此一旦找到第一个满足条件的j,我们就可以停止搜索。
- 对于每个
-
计算R(i) :
- 对于每个
i,我们需要从右往左遍历,寻找第一个a[k] > a[i]且k > i的元素。 - 同样地,找到第一个满足条件的
k后,我们可以停止搜索。
- 对于每个
-
计算MAX(i) :
- 一旦得到了
L(i)和R(i),我们可以计算MAX(i) = L(i) * R(i),并更新最大值。
- 一旦得到了
-
时间复杂度:
- 对于每个
i,我们分别要遍历左侧和右侧的元素,所以整体时间复杂度是O(N^2),其中N是数组的大小。这个复杂度对于N较小的情况是可接受的,但如果N非常大(如上千或上万),需要考虑优化。
- 对于每个
优化的空间
当前的解法在计算L(i)和R(i)时,使用了两次嵌套循环,这使得整体时间复杂度为O(N^2),对于较大的输入,这种解法可能会变得效率低下。为了提高效率,我们可以考虑使用单调栈来优化。
优化方案:单调栈
使用单调栈可以在O(N)时间内解决L(i)和R(i)的计算问题。通过栈,我们可以保证每次弹出的元素都是符合条件的最近的元素。
-
计算L(i) :
- 我们使用一个栈来维护一个单调递减的序列。对于每个
i,如果栈顶元素小于等于a[i],则将栈顶元素弹出,直到栈顶元素大于a[i]。此时栈顶元素即为L(i)。
- 我们使用一个栈来维护一个单调递减的序列。对于每个
-
计算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),从而有效地解决了最大乘积问题。