我们大多数人都熟悉maximum sum subarray problem。我遇到了这个问题的一个变体,要求程序员输出模数为M的所有子阵列总和的最大值。
解决这个变体的天真方法是找到所有可能的子阵列总和(其大小为N ^ 2,其中N是数组的大小)。当然,这还不够好。问题是 - 我们怎样才能做得更好?
示例:让我们考虑以下数组:
6 6 11 15 12 1
令M = 13.在这种情况下,子阵列6 6(或12或6 6 11 15或11 15 12)将产生最大总和(= 12)。
答案 0 :(得分:22)
我们可以这样做:
维护索引为sum
的数组ith
,它包含从0到ith
的模数和。
对于每个索引ith
,我们需要找到以此索引结尾的最大子总和:
对于每个子阵列(start + 1,i),我们知道这个子数组的mod和是
int a = (sum[i] - sum[start] + M) % M
因此,如果sum[i]
大于sum[start]
并且尽可能接近sum[i]
,我们只能获得大于sum[i]
的子和。
如果您使用二叉搜索树,这可以轻松完成。
伪代码:
int[] sum;
sum[0] = A[0];
Tree tree;
tree.add(sum[0]);
int result = sum[0];
for(int i = 1; i < n; i++){
sum[i] = sum[i - 1] + A[i];
sum[i] %= M;
int a = tree.getMinimumValueLargerThan(sum[i]);
result = max((sum[i] - a + M) % M, result);
tree.add(sum[i]);
}
print result;
时间复杂度:O(n log n)
答案 1 :(得分:7)
让 A 成为我们的输入数组,具有从零开始的索引。我们可以在不改变结果的情况下减少 A 模 M 。
首先,让我们通过计算表示 A 的前缀和,模数 M P 来将问题简化为更容易的问题。 >:
A = 6 6 11 2 12 1
P = 6 12 10 12 11 12
现在让我们按递减顺序处理解决方案子阵列的可能左边界。这意味着我们将首先确定从索引 n - 1 开始的最佳解决方案,然后是从索引 n - 2 等开始的最佳解决方案。
在我们的示例中,如果我们选择 i = 3 作为左边界,则可能的子阵列总和由后缀 P [3..n-1] 表示加上常数 a = A [i] - P [i] :
a = A[3] - P[3] = 2 - 12 = 3 (mod 13)
P + a = * * * 2 1 2
全球最大值也会出现在一个点上。由于我们可以从右到左插入后缀值,因此我们现在将问题简化为以下内容:
给定一组值 S 和整数 x 和 M ,找到 S + x 的最大值modulo M
这个很简单:只需使用平衡的二叉搜索树来管理 S 的元素。给定查询 x ,我们希望在 S 中找到小于 M - x 的最大值(即没有溢出的情况)添加 x 时发生。如果没有这样的值,只需使用 S 的最大值。两者都可以在O(log | S |)时间内完成。
此解决方案的总运行时间:O(n log n)
这是一些计算最大总和的C ++代码。它还需要一些小的适应性来返回最佳子阵列的边界:
#include <bits/stdc++.h>
using namespace std;
int max_mod_sum(const vector<int>& A, int M) {
vector<int> P(A.size());
for (int i = 0; i < A.size(); ++i)
P[i] = (A[i] + (i > 0 ? P[i-1] : 0)) % M;
set<int> S;
int res = 0;
for (int i = A.size() - 1; i >= 0; --i) {
S.insert(P[i]);
int a = (A[i] - P[i] + M) % M;
auto it = S.lower_bound(M - a);
if (it != begin(S))
res = max(res, *prev(it) + a);
res = max(res, (*prev(end(S)) + a) % M);
}
return res;
}
int main() {
// random testing to the rescue
for (int i = 0; i < 1000; ++i) {
int M = rand() % 1000 + 1, n = rand() % 1000 + 1;
vector<int> A(n);
for (int i = 0; i< n; ++i)
A[i] = rand() % M;
int should_be = 0;
for (int i = 0; i < n; ++i) {
int sum = 0;
for (int j = i; j < n; ++j) {
sum = (sum + A[j]) % M;
should_be = max(should_be, sum);
}
}
assert(should_be == max_mod_sum(A, M));
}
}
答案 2 :(得分:3)
从您的问题来看,您似乎已经创建了一个存储累积总和的数组(前缀总和数组),并且正在将子数组arr[i:j]
的总和计算为(sum[j] - sum[i] + M) % M
。 (arr和sum分别表示给定数组和前缀sum数组)
计算每个子数组的总和将产生O(n*n)
算法。
出现的问题是-
我们真的需要考虑每个子数组的总和以达到所需的最大值吗?
不!
对于值j
,当(sum[j] - sum[i] + M) % M
刚好大于sum[i]
或差值为sum[j]
时,值M - 1
将是最大。
这会将算法减少到O(nlogn)
。
您可以看一下这个解释! https://www.youtube.com/watch?v=u_ft5jCDZXk
答案 3 :(得分:2)
这是最大子数组求和模的Java代码。我们处理的情况是,我们找不到树中的最小元素,严格地大于s [i]
public static long maxModulo(long[] a, final long k) {
long[] s = new long[a.length];
TreeSet<Long> tree = new TreeSet<>();
s[0] = a[0] % k;
tree.add(s[0]);
long result = s[0];
for (int i = 1; i < a.length; i++) {
s[i] = (s[i - 1] + a[i]) % k;
// find least element in the tree strictly greater than s[i]
Long v = tree.higher(s[i]);
if (v == null) {
// can't find v, then compare v and s[i]
result = Math.max(s[i], result);
} else {
result = Math.max((s[i] - v + k) % k, result);
}
tree.add(s[i]);
}
return result;
}
答案 4 :(得分:1)
使用O(n * log(n))
的总java实现Object.keys
答案 5 :(得分:1)
根据@Pham Trung建议的解决方案添加STL C ++ 11代码。可能会派上用场。
#include <iostream>
#include <set>
int main() {
int N;
std::cin>>N;
for (int nn=0;nn<N;nn++){
long long n,m;
std::set<long long> mSet;
long long maxVal = 0; //positive input values
long long sumVal = 0;
std::cin>>n>>m;
mSet.insert(m);
for (long long q=0;q<n;q++){
long long tmp;
std::cin>>tmp;
sumVal = (sumVal + tmp)%m;
auto itSub = mSet.upper_bound(sumVal);
maxVal = std::max(maxVal,(m + sumVal - *itSub)%m);
mSet.insert(sumVal);
}
std::cout<<maxVal<<"\n";
}
}
答案 6 :(得分:1)
对我来说,这里的所有解释都很糟糕,因为我没有找到搜索/排序部分。尚不清楚我们如何搜索/排序。
我们都知道我们需要构建prefixSum
,即sum of all elems from 0 to i with modulo m
我想,我们正在寻找的是明确的。
知道subarray[i][j] = (prefix[i] - prefix[j] + m) % m
(表示从索引i到j的模和),给定prefix [i]时,我们的最大值始终是该prefix [j],该前缀与j [i]尽可能接近,但稍大一些。
例如对于m = 8,prefix [i]为5,我们正在寻找5之后的下一个值,它在我们的prefixArray中。
为了进行高效搜索(二进制搜索),我们对前缀进行排序。
我们不能做的是,首先构建prefixSum,然后再次从0迭代到n,然后在排序的前缀数组中寻找索引,因为我们可以找到和小于我们的startIndex的endIndex,这不好。
因此,我们要做的是从0迭代到n,指示潜在的最大子数组总和的 endIndex ,然后查看排序后的前缀数组(开头为空),其中包含前缀在0到endIndex之间排序。
def maximumSum(coll, m):
n = len(coll)
maxSum, prefixSum = 0, 0
sortedPrefixes = []
for endIndex in range(n):
prefixSum = (prefixSum + coll[endIndex]) % m
maxSum = max(maxSum, prefixSum)
startIndex = bisect.bisect_right(sortedPrefixes, prefixSum)
if startIndex < len(sortedPrefixes):
maxSum = max(maxSum, prefixSum - sortedPrefixes[startIndex] + m)
bisect.insort(sortedPrefixes, prefixSum)
return maxSum
答案 7 :(得分:1)
这里已经列出了很多很棒的解决方案,但我想添加一个具有 O(nlogn) 运行时的解决方案,而无需使用 Python 标准库中没有的平衡二叉树。这个解决方案不是我的想法,但我不得不思考一下它为什么起作用。这是代码,解释如下:
def maximumSum(a, m):
prefixSums = [(0, -1)]
for idx, el in enumerate(a):
prefixSums.append(((prefixSums[-1][0] + el) % m, idx))
prefixSums = sorted(prefixSums)
maxSeen = prefixSums[-1][0]
for (a, a_idx), (b, b_idx) in zip(prefixSums[:-1], prefixSums[1:]):
if a_idx > b_idx and b > a:
maxSeen = max((a-b) % m, maxSeen)
return maxSeen
与其他解决方案一样,我们首先计算前缀和,但这次我们也跟踪前缀和的索引。然后我们对前缀和进行排序,因为我们想要找到模 m 的前缀和之间的最小差异 - 排序让我们只查看相邻元素,因为它们具有最小的差异。
此时您可能认为我们忽略了问题的一个重要部分 - 我们希望前缀和之间的差异最小,但较大的前缀和需要出现在较小的前缀和之前(意味着它具有较小的索引) .在使用树的解决方案中,我们通过一一添加前缀和并重新计算最佳解决方案来确保。
然而,事实证明我们可以查看相邻元素而忽略不满足我们索引要求的元素。这让我困惑了一段时间,但关键的认识是最佳解决方案总是来自两个相邻的元素。我将通过一个矛盾来证明这一点。假设最优解来自两个不相邻的前缀和 x 和 z,索引为 i 和 k,其中 z > x(已排序!)和 k > i:
x ... z
k ... i
让我们考虑 x 和 z 之间的数字之一,我们称它为 y,索引为 j。由于列表已排序,因此 x < y < z.
x ... y ... z
k ... j ... i
前缀和 y 必须具有索引 j < i,否则它将成为带有 z 的更好解决方案的一部分。但是如果 j < i,则 j < k 和 y 和 x 形成比 z 和 x 更好的解决方案!所以 x 和 z 之间的任何元素都必须与两者之一形成更好的解,这与我们最初的假设相矛盾。因此最优解必须来自排序列表中相邻的前缀和。
答案 8 :(得分:0)
您可以在Wikipedia中看到一种解决方案,称为Kadane算法,该算法计算所有子数组 i <的最大子数组总和,并观察以子元素 i 结尾的最大子数组/ em>通过遍历数组一次。然后,这解决了运行时复杂度为O(n)的问题。
不幸的是,我认为当存在多个解决方案时,Kadane的算法无法找到所有可能的解决方案。
一个Java实现,我没有对其进行测试:
public int[] kadanesAlgorithm (int[] array) {
int start_old = 0;
int start = 0;
int end = 0;
int found_max = 0;
int max = array[0];
for(int i = 0; i<array.length; i++) {
max = Math.max(array[i], max + array[i]);
found_max = Math.max(found_max, max);
if(max < 0)
start = i+1;
else if(max == found_max) {
start_old=start;
end = i;
}
}
return Arrays.copyOfRange(array, start_old, end+1);
}
答案 9 :(得分:0)
我觉得我的想法与已经发布的内容一致,但以防万一-Kotlin O(NlogN)解决方案:
val seen = sortedSetOf(0L)
var prev = 0L
return max(a.map { x ->
val z = (prev + x) % m
prev = z
seen.add(z)
seen.higher(z)?.let{ y ->
(z - y + m) % m
} ?: z
})
答案 10 :(得分:0)
在java中使用treeset实现...
handleChange = (event) => {
this.setState({[event.target.name]: event.target.value});
};
公共类 Main {
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.TreeSet;
}
答案 11 :(得分:0)
这是一个在java中解决这个问题的实现,它使用java中的TreeSet来优化解决方案!
public static long maximumSum2(long[] arr, long n, long m)
{
long x = 0;
long prefix = 0;
long maxim = 0;
TreeSet<Long> S = new TreeSet<Long>();
S.add((long)0);
// Traversing the array.
for (int i = 0; i < n; i++)
{
// Finding prefix sum.
prefix = (prefix + arr[i]) % m;
// Finding maximum of prefix sum.
maxim = Math.max(maxim, prefix);
// Finding iterator poing to the first
// element that is not less than value
// "prefix + 1", i.e., greater than or
// equal to this value.
long it = S.higher(prefix)!=null?S.higher(prefix):0;
// boolean isFound = false;
// for (long j : S)
// {
// if (j >= prefix + 1)
// if(isFound == false) {
// it = j;
// isFound = true;
// }
// else {
// if(j < it) {
// it = j;
// }
// }
// }
if (it != 0)
{
maxim = Math.max(maxim, prefix - it + m);
}
// adding prefix in the set.
S.add(prefix);
}
return maxim;
}
答案 12 :(得分:-1)
修改Kadane algorithm以跟踪#occurrence。以下是代码。
#python3
#source: https://github.com/harishvc/challenges/blob/master/dp-largest-sum-sublist-modulo.py
#Time complexity: O(n)
#Space complexity: O(n)
def maxContiguousSum(a,K):
sum_so_far =0
max_sum = 0
count = {} #keep track of occurrence
for i in range(0,len(a)):
sum_so_far += a[i]
sum_so_far = sum_so_far%K
if sum_so_far > 0:
max_sum = max(max_sum,sum_so_far)
if sum_so_far in count.keys():
count[sum_so_far] += 1
else:
count[sum_so_far] = 1
else:
assert sum_so_far < 0 , "Logic error"
#IMPORTANT: reset sum_so_far
sum_so_far = 0
return max_sum,count[max_sum]
a = [6, 6, 11, 15, 12, 1]
K = 13
max_sum,count = maxContiguousSum(a,K)
print("input >>> %s max sum=%d #occurrence=%d" % (a,max_sum,count))