给定一个整数数组,找到 O(n)中所有段(间隔)中所有最大数字的总和。
答案 0 :(得分:2)
接下来是C ++源代码,程序读取标准输入或a 其路径作为命令行上的第一个参数提供的文件。
输入文件的格式应为:
编译通过以下方式完成:
g++ -std=c++14 -g -Wall -O0 solution.cpp -o solution
程序将首先使用O(n)
算法计算总和,然后使用。{
用于验证的O(n^3)
算法。
示例运行:
$ ./solution.exe
3
4 5 6
O(n) sum: 32
O(n^3) sum: 32
源代码:
#include <cstdio>
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
int main(int argc, char* argv[]) {
if(argc > 1)
freopen(argv[1], "r", stdin);
// load input array
int N;
cin >> N;
vector<int> A(N);
for(auto& ai : A)
cin >> ai;
// compute sum max of all subarrays in O(n)
vector<int> l(N);
vector<int> r(N);
stack<int> s;
for(int i=0; i<N; ++i) {
while(s.size() && A[s.top()] < A[i]) {
r[s.top()] = i;
s.pop();
}
s.push(i);
}
while(s.size()) {
r[s.top()] = N;
s.pop();
}
for(int i=N-1; i>=0; --i) {
while(s.size() && A[s.top()] <= A[i]) {
l[s.top()] = i;
s.pop();
}
s.push(i);
}
while(s.size()) {
l[s.top()] = -1;
s.pop();
}
int sum = 0;
for(int i=0; i<N; ++i) {
int cs = A[i]*(i-l[i])*(r[i]-i);
sum += cs;
}
cout << "O(n) sum: " << sum << '\n';
// compute sum using O(n^3) algorithm for verification
sum = 0;
for(int i=0; i<N; ++i) {
for(int j=i; j<N; ++j) {
int cs = *max_element(begin(A)+i, begin(A)+j+1);
sum += cs;
}
}
cout << "O(n^3) sum: " << sum << '\n';
}
首先,这不是完整的证明。我有一个证据,但它太多了 参与数学符号被包含在没有mathjs的网站上 支持......我将给出证明的草图,并将详细信息提供给 读者(我知道这很蹩脚)。
该解决方案使用多个技巧:
让我们命名问题的元素:
A
。值A[i]
是数组中基于0的索引i
的值。n
。该数组从索引0
扩展到n-1
。首先,我定义了一个子数组的 leader ,这是索引i
子数组中元素的值,使A[i]
的值最大
子数组和索引j < i
的子数组中的每个元素都有
A[j]<A[i]
。直观地说,子数组的 leader 是其第一个的索引
最大值。
我说 leader 的相等性定义了equivalence relation 子阵列。 (作为练习留下的证据)。
由此,我们知道了集合的等价类form a partition 所有子阵列。此外,等价类中的所有元素都具有 相同的最大值(由于 leader 函数的定义)。
等价类E_i
的大小, leader 的所有子数组的集合
是i
,可以从值中轻松计算出来:
l(i)
这是i
左侧的第一个索引A[l(i)] >= A[i]
或。{
如果不存在这样的索引,则为-1 r(i)
这是i
右侧A[l(i)] > A[i]
或n
右侧的第一个索引
如果不存在此类索引,则E_i
使用这些表示法(i-l(i))*(r(i)-i)
的基数为:l(i)
。证据留给读者作为练习。
现在是计算值r(i)
和l(i)
的编程技巧。作为
计算几乎相同我只会解释i
的计算。我们
使用以下不变量来维护索引:
我们从左到右扫描阵列。对于每个索引i
,我们检查它的值
大于当前堆栈顶部的值。如果是这种情况,则意味着其 leader 是堆栈顶部的子阵列无法扩展到右侧的当前索引。因此,我们将 r(堆栈顶部)的值更新为i
。我们弹出堆栈顶部,因为它可能不会涉及任何超过A[top of stack] >= A[i]
的子数组。
我们继续更新 leader 并弹出堆栈顶部,直到
堆栈为空或i
。然后我们在堆栈上推送r
。
到达数组末尾时,堆栈中可能仍有一些索引。
这意味着它们参与延伸到数组末尾的子数组。
我们将N
值更新为r()
。
整个扫描会更新O(n)
中while
的所有值。这是因为每一个
元素是
由于我们不能多次弹出一个元素,因此内部n
循环不会运行
整个阵列扫描的时间超过l()
次。
相同的过程用于计算-1
,但以下情况除外:
O(n)
来表示限制是数组的界限然后我们可以应用公式来计算等价的大小
类和在我们的总结中使用它。像我们一样导致O(1)
算法
需要:
r(i)
阅读O(1)
l(i)
阅读O(1)
A(i)
在E_i
答案 1 :(得分:0)
问题的复杂性是 not O(N),因为长度为N的数据集中的序列数为N * (N+1) / 2
,可以很容易地显示出来:
N = 1:1序列([1]) - &gt; 1 * 2/2 = 1
N = 2:3序列([1],[2],[1,2]) - > 2 * 3/2 = 6/2 = 3
N = 3:6序列([1],[2],[3],[1,2],[2,3],[1,2,3]) - > 3 * 4/2 = 12/2 = 6
为了找到所有序列的最大值之和,需要迭代所有序列。证明完毕更新或者不......请参阅fjardon的回答,欣赏等价类的力量。
话说回来,你可以利用这样一个事实来避免额外的复杂性:在位置i0,长度为w的序列的最大值为max (seq i0 (w-1)) data.[i0+w-1])
。
这导致了下面F#片段的实现:
let maxsum (a: int[]) : int =
let mutable x = 0
let mutable total = 0
let n = Array.length a
for i0 in 0..n-1 do
x <- a.[i0]
total <- total + x
for w in 2..n-i0 do
x <- max x a.[i0+w-1]
total <- total + x
total