问题描述
小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) 的最大值是多少。
问题分析和解答
- 首先理解
L(i)和R(i)的定义:L(i)是满足j < i且a[j] > a[i]的最大的j值,而R(i)是满足k > i且a[k] > a[i]的最小的k值。 - 其次计算出
L(i)和R(i),那么就不难再得出max(i)了。 - 那么在这里怎么求出
L(i)和R(i)呢,首先能想到的就是暴力模拟了通过俩层for循环直接暴力求出L(i)和R(i)代码如下:
代码
#include "iostream"
#include "vector"
int solution(int n, std::vector<int> a) {
std::vector<int> l(n, 0);
std::vector<int> r(n, 0);
// 计算L(i)
for (int i = 0; i < n; i++) {
for (int j = i - 1; j >= 0; j--) {
if (a[j] > a[i]) {
l[i] = j + 1;
break;
}
}
}
// 计算R(i)
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (a[j] > a[i]) {
r[i] = j + 1;
break;
}
}
}
// 计算MAX(i)并找到最大值
int m = l[0] * r[0];
for (int i = 1; i < n; i++) {
if (l[i] * r[i]> m) {
m = l[i] * r[i];
}
}
return m;
}
int main() {
// Add your test cases here
std::cout << (solution(5, {5, 4, 3, 4, 5}) == 8) << std::endl;
return 0;
}
- 这里不难发现时间复杂度会很高两次的遍历都是O(N^2),如果题目对时间要求过高的话代码是ac不了的,那有没有更快的方法呢当然有
- 我们仔细理解题目会发现,只要满足条件就可以得出俩个条件数组,如果有一个数据结构可以直接在满足条件时直接存储数据,那就是队列和栈,当然这题栈会更加方便,它能够高效地处理数组中元素的相对大小关系。
代码如下:
#include "iostream"
#include "vector"
#include <stack>
int solution(int n, std::vector<int> a) {
std::vector<int> l(n, 0);
std::vector<int> r(n, 0);
std::stack<int> s;
//计算L(i)
for (int i = 0; i < n; i++) {
while (!s.empty() && a[s.top()] <= a[i]) {
s.pop();
}
if (!s.empty()) {
l[i] = s.top() + 1;
}
s.push(i);
}
while (!s.empty()) s.pop();
// 计算R(i)
for (int i = n - 1; i >= 0; i--) {
while (!s.empty() && a[s.top()] <= a[i]) {
s.pop();
}
if (!s.empty()) {
r[i] = s.top() + 1;
}
s.push(i);
}
//计算 max
int m = l[0] * r[0];
for (int i = 1; i < n; i++) {
if (l[i] * r[i]> m) {
m = l[i] * r[i];
}
}
return m;
}
int main() {
// Add your test cases here
std::cout << (solution(5, {5, 4, 3, 4, 5}) == 8) << std::endl;
return 0;
}
总结和心得
- 在代码中核心要确保栈中的元素始终是递减的。在计算
L(i)和R(i)之间要记得清空栈,同时确保L[i]和R[i]都是有效的。 - 这两种方法对比下发现,如果一个题可以用更加符合的方法来处理不仅在代码运行起来复杂度更低,代码也会更加简练,因此,写题目应当考虑更加全面,同时,自身也应该掌握更多内容。
- 这道题目也是很典型的栈的题目的应用,在此题中更加深刻认识了栈这种数据结构。