求连续子数组和的最大值的变种问题
2014-11-10 19:13
267 查看
求连续子数组和的最大值,是一个很经典的问题,网上有很多文章来介绍这个问题,我们先简要介绍一下这个问题的动态规划解法。
对于一个输入数组a
,我们用res表示该数组连续子数组和的最大值,用sum[i]表示前i个元素中,包含第i个元素且和最大的连续子数组的和,这样我们可以从头到尾遍历数组,执行以下公式:
sum[i + 1] = max (a[i + 1], sum[i])
res= max (res, sum[i + 1])
对于sum数组,我们在一个时刻只会使用一个值,可以简化为一个数,具体代码如下所示:
变种一:求连续子数组和的绝对值的最小值
这个问题乍看一下,不是也能用动态规划的思想来解决吗?解法如下:
用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
对于一个输入数组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
相关文章推荐
- 经典算法——连续子数组最大和问题
- [算法导论]练习4.1-5最大连续子数组问题
- 动态规划求解连续子数组最大和问题(应该是新的描述方法?)
- LeetCode问题53:最大的连续子数组和
- 分治策略__解决最大连续子数组的问题
- 连续子数组最大和问题
- 求解最大连续子数组问题
- 面试题之连续子数组的最大和问题,矩形覆盖问题
- PHP实现求连续子数组最大和问题2种解决方法
- 连续子数组的最大和问题(一维和二维)To the Max (POJ 1050)
- 连续子数组最大和问题(扫描法改进)
- 求连续子数组最大和问题的两种解法_PHP实现
- 最大连续子数组问题
- 最大连续子数组问题-homework-01
- 最大连续子数组和问题
- 最大连续子数组问题2-homework-02
- 求连续子数组的最大和问题
- Demo003 最大连续子数组问题(《算法导论》4.1-5)
- JAVA代码—算法基础:最大连续子数组乘积问题
- 动态规划问题系列---连续子数组(二维)的最大和