您的位置:首页 > 其它

求连续子数组和的最大值的变种问题

2014-11-10 19:13 246 查看
求连续子数组和的最大值,是一个很经典的问题,网上有很多文章来介绍这个问题,我们先简要介绍一下这个问题的动态规划解法。

对于一个输入数组a
,我们用res表示该数组连续子数组和的最大值,用sum[i]表示前i个元素中,包含第i个元素且和最大的连续子数组的和,这样我们可以从头到尾遍历数组,执行以下公式:

sum[i + 1] = max (a[i + 1], sum[i])

res= max (res, sum[i + 1])

对于sum数组,我们在一个时刻只会使用一个值,可以简化为一个数,具体代码如下所示:

int solve(int[] a) {
if (a == null || a.length == 0) {
return -1;
}
int sum = a[0], res = a[0];
for (int i = 1; i < a.length; i++) {
if (sum < 0) {
sum = 0;
}
sum += a[i];
res = Math.max(sum, res);
}
return res;
}
但是,这个问题并不是本文所讨论的重点,我们要讨论的是该问题的两个变种问题。

变种一:求连续子数组和的绝对值的最小值

这个问题乍看一下,不是也能用动态规划的思想来解决吗?解法如下:

用res表示该数组连续子数组和的绝对值的最小值,用sum[i]表示前i个元素中,包含第i个元素且和的绝对值最小的连续子数组的和,这样依然可以从头到尾遍历数组,执行以下公式:

sum[i + 1] = sum[i] * a[i + 1] >= 0 ? a[i + 1] : sum[i] + a[i + 1]

res = min (res, sum[i + 1])

这个方法的思想就是,如果sum[i]和a[i + 1]正负符号不同,则a[i + 1]加上sum[i]会有可能更接近0,从而使res有可能会更小;否则我们就直接舍弃sum[i],直接令sum[i + 1]等于a[i + 1],因为加上sum[i]会使sum[i + 1]更加偏离0。

上面方法介绍完了,看上去很有道理啊,但是非常不幸的是,这个方法是错误的。比如说,对于-5,-5,10构成的数组,按这种方法得到的结果为5,但实际上的结果为0。这是因为这个方法采用的是贪心的策略,并没有满足动态规划的最优子结构的条件,即sum[i + 1]为最优解并不能保证它的子问题sum[i]也是最优解。

既然我们不能使用动态规划来求解了,而暴力法需要O(n^2)的时间复杂度,那有没有好一些的方法呢?

我们试着求数组a
的前缀和数组b[n + 1],即

b[0] = 0

b[1] = a[0] + b[0]

b[2] = a[1] + b[1]

...

b
= a[n - 1] + b[n - 1]

可以看出,a[i] + a[i + 1] + ... + a[i + k] = b[i + k + 1] - b[i],这样我们可以把求数组a的连续子数组和的绝对值的最小值的问题,转化为求前缀数组b中两两之差的绝对值最小值,然后我们对数组b进行排序,然后比较相邻两个元素差的绝对值,得到的最小值便是数组a的连续子数组和的绝对值的最小值。时间复杂度方面,数组a转换为数组b需要O(n)的时间,数组b排序需要O(nlog(n))的时间,比较数组b相邻两个元素差的绝对值需要O(n)的时间,因此整体的时间复杂度为O(nlog(n))。

变种二:求环形数组连续子数组和的最大值

环形数组相比于非环形数组,它多了一些可能的情况,即非环形数组中前面一小部分和后面一小部分组成的连续子数组,而我们用原来的方法,是无法计算这种情况的。

对于环形数组a
,我们用sum(i, j)表示从下标i到j - 1的连续子数组和,则对于连续子数组和的最大值为sum(0, i) + sum(j, n)的情况(其中i < j),由于数组a的总和是固定值,我们可以将问题进行转换,用原来的方法来求该连续子数组和的最小值sum(i, j),然后拿总和减去这个最小值,就能得到这种情况的最大值。这样我们分别用原来的方法,分别计算一次连续子数组和的最大值max和最小值min,以及数组所有元素之和total,这样环形数组连续子数组和的最大值就为max和total
- min中的较大者。这种方法的时间复杂度依然为O(n)。

转载请注明出处:/article/7757410.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: