这个Stack Overflow问题再次让我感到讨厌 :
这是John寻找Java解决方案的问题:
在将N个元素的数组初始化为0的情况下,我们得到了M个操作序列
(p; q; r)
。 运算(p; q; r)
表示应将整数r添加到所有数组元素A[p];A[p + 1]; : : : ;A[q]
A[p];A[p + 1]; : : : ;A[q]
。 您将输出执行所有M运算将导致的数组中的最大元素。 有一个简单的解决方案,可以简单地执行所有操作,然后返回最大值,这需要O(MN)
时间。 我们正在寻找一种更有效的算法。
有趣。 确实,一个幼稚的解决方案仅会按要求执行所有操作。 另一个幼稚但不太幼稚的解决方案会将所有(p; r)
和所有(q + 1; -r)
的操作转换为(x; y)
形式的信号。 换句话说,我们可以这样简单地实现我提出的解决方案 :
// This is just a utility class to model the ops
class Operation {
final int p;
final int q;
final int r;
Operation(int p, int q, int r) {
this.p = p;
this.q = q;
this.r = r;
}
}
// These are some example ops
Operation[] ops = {
new Operation(4, 12, 2),
new Operation(2, 8, 3),
new Operation(6, 7, 1),
new Operation(3, 7, 2)
};
// Here, we're calculating the min and max
// values for the combined values of p and q
IntSummaryStatistics stats = Stream
.of(ops)
.flatMapToInt(op -> IntStream.of(op.p, op.q))
.summaryStatistics();
// Create an array for all the required elements using
// the min value as "offset"
int[] array = new int[stats.getMax() - stats.getMin()];
// Put +r and -r "signals" into the array for each op
for (Operation op : ops) {
int lo = op.p - stats.getMin();
int hi = op.q + 1 - stats.getMin();
if (lo >= 0)
array[lo] = array[lo] + op.r;
if (hi < array.length)
array[hi] = array[hi] - op.r;
}
// Now, calculate the prefix sum sequentially in a
// trivial loop
int maxIndex = Integer.MIN_VALUE;
int maxR = Integer.MIN_VALUE;
int r = 0;
for (int i = 0; i < array.length; i++) {
r = r + array[i];
System.out.println((i + stats.getMin()) + ":" + r);
if (r > maxR) {
maxIndex = i + stats.getMin();
maxR = r;
}
}
System.out.println("---");
System.out.println(maxIndex + ":" + maxR);
上面的程序将打印出:
2:3
3:5
4:7
5:7
6:8
7:8
8:5
9:2
10:2
11:2
---
6:8
因此,最大值在位置6生成,该值为8。
Java 8中更快的计算
使用Java 8的新Arrays.parallelPrefix()操作可以更快地计算出该值。 而不是最后循环,只需编写:
Arrays.parallelPrefix(array, Integer::sum);
System.out.println(
Arrays.stream(array).parallel().max());
很棒,因为它可以比顺序O(M+N)
解决方案运行得更快。 在此处阅读有关前缀总和的信息 。
现在向我展示承诺SQL代码
在SQL中,可以轻松地重新实现天真的顺序复杂度和线性复杂度解决方案,而我正在展示PostgreSQL解决方案。
我们该怎么做? 我们在这里使用了几个功能。 首先,我们使用公用表表达式(也称为WITH
子句) 。 我们使用它们来声明表变量。 第一个变量是op
表,其中包含我们的操作指令,例如Java:
WITH
op (p, q, r) AS (
VALUES
(4, 12, 2),
(2, 8, 3),
(6, 7, 1),
(3, 7, 2)
),
...
这是微不足道的。 实际上,我们只是在生成几个示例值。
第二个表变量是信号表,在此我们使用前面描述的优化方法,将+r
信号放置在所有p
位置,将-r
信号放置在所有q + 1
位置:
WITH
...,
signal(x, r) AS (
SELECT p, r
FROM op
UNION ALL
SELECT q + 1, -r
FROM op
)
...
运行时:
SELECT * FROM signal ORDER BY x
您将简单地得到:
x r
------
2 3
3 2
4 2
6 1
8 -2
8 -1
9 -3
13 -2
现在,我们需要做的是计算一个运行总计(与前缀总和基本相同) ,如下所示:
SELECT x, SUM(r) OVER (ORDER BY x)
FROM signal
ORDER BY x
x r
------
2 3
3 5
4 7
6 8
8 5
8 5
9 2
13 0
现在只需找到r
的最大值,就可以设置好了。 我们将通过使用ORDER BY
和LIMIT
来使用快捷方式:
SELECT x, SUM(r) OVER (ORDER BY x) AS s
FROM signal
ORDER BY s DESC
LIMIT 1
我们回来了:
x r
------
6 8
完善! 这是完整的查询:
WITH
op (p, q, r) AS (
VALUES
(4, 12, 2),
(2, 8, 3),
(6, 7, 1),
(3, 7, 2)
),
signal(x, r) AS (
SELECT p, r
FROM op
UNION ALL
SELECT q + 1, -r
FROM op
)
SELECT x, SUM(r) OVER (ORDER BY x) AS s
FROM signal
ORDER BY s DESC
LIMIT 1
您能否击败此SQL解决方案的简洁性? 我敢打赌你不能。 挑战者应在评论部分中写其他选择。
翻译自: https://www.javacodegeeks.com/2016/03/time-funky-sql-prefix-sum-calculation.html