题目
算法流程对于长度为N的数组A,求连续子数组的和最接近0的值。
如:
数组A:1,-2,3,10,-4,7,2,-5
它是所有子数组中,和最接近0的是哪个?
申请比A长1的空间sum[-1,0,...,N-1],sum[i]是A的前i项和。定义sum[-1]=0
显然有:A的i到j项和=sum(j)-sum(i-1)
算法思路:
对sum[-1,0,...,N-1]排序,然后计算sum相邻元素的差的绝对值,最小即为所求
在A中任意取两个前缀子数组的和,求差的最小值。
讨论
计算前n项和数组sum和计算sum相邻元素差的时间复杂度,都是O(N),排序的时间复杂度认为是O(N*logN),因此,总时间复杂度:O(NlogN)。
思考:如果需要返回绝对值最小的子数组本身呢?
代码如下
int MinSubarray(const int* a, int size)
{
int* sum = new int[size + 1];
sum[0] = 0;
int i;
for (i = 0; i < size; i++)
{
sum[i + 1] = sum[i] + a[i];
}
sort(sum, sum + size + 1);
int difference = abs(sum[1] - sum[0]);
int result = difference;
for (i = 1; i < size; i++)
{
difference = abs(sum[i + 1] - sum[i]);
result = min(difference, result);
}
delete[] sum;
return result;
}
下面来看一下最大连续子数组的问题
给定一个数组A[0,...,n-1],求A的连续子数组,使得该子数组的和最大。
例如:数组1,-2,3,10,-4,7,2,-5最大子数组:3,10,-4,7,2
分析
代码如下记S[i]为以A[i]结尾的数组中和最大的子数组,则:S[i+1]=max(S[i]+A[i+1],A[i+1])
S[0]=A[0]
遍历i:0<=i<=n-1
动态规划:最优子问题
时间复杂度O(n)
int MaxSubarray(const int* a, int size)
{
if (!a || (size <= 0))
return 0;
int sum = a[0]; // 当前子串的和
int result = sum; // 当前找到的最优解
for (int i = 1; i < size; i++)
{
if (sum > 0)
{
sum += a[i];
}
else
{
sum = a[i];
}
result = max(sum, result);
}
return result;
}
求最大子数组还有一种思路如下:
定义:前缀和sum[i]=a[0]+a[1]+...+a[i]则:a[i,j]=sum[j]-sum[i-1](定义p[-1]=0),最大子数组=sum(j)-sum(i-1)
算法过程
- 求i前缀sum[i]:
- 遍历i:0<=i<=n-1
- sum[i]=sum[i-1]+a[i]
- 计算以a[j]结尾的子数组的最大值
- 对于某个j:遍历0<=i<=j,求sum[i]的最小值m
- sum[j]-m即为a[j]结尾的数组中最大的子数组的值
- 统计sum[j]-m的最大值,0<=j<=n-1
- 1,2,3步都是线性的,因此,时间复杂度O(n)。
如果要求子数组本身,只需要记录一下from和to就可以了