我想知道如何在序列中得到最长的正和子序列:
例如我有-6 3 -4 4 -5,所以最长的正子序列是3 -4 4.实际上总和为正(3),我们不能加-6而不是-5或者它会变成负面的。
它可以很容易地在O(N ^ 2)中解决,我认为可以存在更快的东西,比如O(NlogN)
你有什么想法吗?
编辑:必须保留订单,您可以跳过子字符串中的任何数字
EDIT2:如果我使用术语" sebsequence"引起混淆,我很抱歉,因为@beaker指出我的意思是子串
答案 0 :(得分:3)
O(n)
空间和时间解决方案,将从代码开始(抱歉,Java ;-)并尝试稍后解释:
public static int[] longestSubarray(int[] inp) {
// array containing prefix sums up to a certain index i
int[] p = new int[inp.length];
p[0] = inp[0];
for (int i = 1; i < inp.length; i++) {
p[i] = p[i - 1] + inp[i];
}
// array Q from the description below
int[] q = new int[inp.length];
q[inp.length - 1] = p[inp.length - 1];
for (int i = inp.length - 2; i >= 0; i--) {
q[i] = Math.max(q[i + 1], p[i]);
}
int a = 0;
int b = 0;
int maxLen = 0;
int curr;
int[] res = new int[] {-1,-1};
while (b < inp.length) {
curr = a > 0 ? q[b] - p[a-1] : q[b];
if (curr >= 0) {
if(b-a > maxLen) {
maxLen = b-a;
res = new int[] {a,b};
}
b++;
} else {
a++;
}
}
return res;
}
A
n
进行操作
P
定义为包含前缀sum的数组,直到索引为i
所以P[i] = sum(0,i)
其中`i = 0,1,...,n-1& #39; u < v
和P[u] <= P[v]
则u
永远不会成为我们的终点Q
和Q[n-1] = P[n-1]
Q[i] = max(P[i], Q[i+1])
M_{a,b}
,它会向我们显示从a
开始到b
或更高结束的最大总和子阵列。我们知道M_{0,b} = Q[b]
和M_{a,b} = Q[b] - P[a-1]
a, b = 0
并开始移动它们。如果M
的当前值大于或等于0,那么我们知道我们将找到(或已经找到)一个sum> = 0的子阵列,然后我们只需要将b-a
与之前的比较发现长度。否则,没有从a
开始并且遵守我们的约束的子数组,因此我们需要增加a
。答案 1 :(得分:1)
让我们做一个天真的实现,然后改进它。
我们从左向右移动计算部分和,并且对于每个位置,我们发现最左边的部分和,例如当前部分和大于此。
input a
int partialSums[len(a)]
for i in range(len(a)):
partialSums[i] = (i == 0 ? 0 : partialSums[i - 1]) + a[i]
if partialSums[i] > 0:
answer = max(answer, i + 1)
else:
for j in range(i):
if partialSums[i] - partialSums[j] > 0:
answer = max(answer, i - j)
break
这是O(n 2 )。现在找到最左边的&#34;好&#34; sum可以通过BST实际维护,其中每个节点将表示为一对(partial sum, index)
,并通过partial sum
进行比较。此外,每个节点都应支持一个特殊字段min
,该字段将是此子树中indices
的最小值。
现在,我们可以使用当前的部分和作为后续三个规则的密钥来降低BST,而不是直接搜索适当的部分和(假设C
是当前节点,L
和R
分别是左右子树的根源:
curMin
中的部分总和,最初为+∞
。C.partial_sum
是&#34;好&#34;然后使用curMin
更新C.index
。R
,请使用curMin
更新L.min
。然后使用answer
更新i - curMin
,同时将当前部分金额添加到BST。
那会给我们O(n * log n)。
答案 2 :(得分:0)
我们可以轻松获得最长子序列的O(n log n)解决方案。
首先,对数组进行排序,记住它们的索引。
选择所有最大的数字,当它们的总和为负时停止,并且你有答案。
恢复原始订单。
伪代码
sort(data);
int length = 0;
long sum = 0;
boolean[] result = new boolean[n];
for(int i = n ; i >= 1; i--){
if(sum + data[i] <= 0)
break;
sum += data[i];
result[data[i].index] = true;
length++;
}
for(int i = 1; i <= n; i++)
if(result[i])
print i;
因此,我将提出一个O(n log n)解决方案,用于最长的正子串,而不是等待。
首先,我们创建一个数组prefix
,它是数组的前缀和。
其次,我们使用二元搜索来寻找具有正和的最长长度
伪代码
int[]prefix = new int[n];
for(int i = 1; i <= n; i++)
prefix[i] = data[i];
if(i - 1 >= 1)
prefix[i] += prefix[i - 1];
int min = 0;
int max = n;
int result = 0;
while(min <= max){
int mid = (min + max)/2;
boolean ok = false;
for(int i = 1; i <= n; i++){
if(i > mid && pre[i] - pre[i - mid] > 0){//How we can find sum of segment with mid length, and end at index i
ok = true;
break;
}
}
if(ok){
result = max(result, mid)
min = mid + 1;
}else{
max = mid - 1;
}
}
好的,所以上面的算法是错误的,正如 piotrekg2 所指出的,我们需要做的是
创建一个数组prefix
,它是数组的前缀和。
对prefix
数组进行排序,我们需要记住前缀数组的索引。
迭代前缀数组,存储到目前为止我们遇到的最小索引,索引之间的最大差异就是答案。
注意:当我们比较prefix
中的值时,如果两个索引的等值,那么较小的索引将会被认为是更大,这将避免总和为0时的情况。
伪代码:
class Node{
int val, index;
}
Node[]prefix = new Node[n];
for(int i = 1; i <= n; i++)
prefix[i] = new Node(data[i],i);
if(i - 1 >= 1)
prefix[i].val += prefix[i - 1].val;
sort(prefix);
int min = prefix[1].index;
int result = 0;
for(int i = 2; i <= n; i ++)
if(prefix[i].index > min)
result = max(prefix[i].index - min + 1, result)
min = min(min, prefix[i].index);