数组分割——解题笔记
题目:有一个没有排序、元素个数为2n的正整数数组,要求:如何能把这个数组分割为元素个数为n的两个数组,并使两个子数组的和最接近。
分析:这道题目可以用动态规划求解,或者说是一个典型的0,1背包问题,对于第i的数,到底是放进去还是不放,就要看放了对结果有什么影响,不放对结果又有什么影响。而结果是依据题目而言的,这道题目中的结果就是数组之和。
注意,一般动态规划数组中,需要先初始化所有i的结果,初始化可以为1,或者0. 然后,从前往后,依次得到每个i的优化结果,而且需要注意的是,第i个的结果只和第i-1个的结果有关。
在这道题目中,我参考了参考中的一篇博文,感觉讲的比较清楚,如下:
给出的伪代码为:
F[][][]← 0 for i ← 1 to 2*N nLimit ← min(i,N) do for j ← 1 to nLimit do for k ← 1 to Sum/2 F[i][j][k] ← F[i-1][j][k] if (k >= A[i] && F[i][j][k] < F[i-1][j-1][k-A[i]]+A[i]) then F[i][j][k] ← F[i-1][j-1][k-A[i]]+A[i] return F[2N][N][Sum/2]
核心函数如下:
int splitArray(int a[], int len, int sum) { int*** X; X = new int**[len+1]; for(int p = 0; p < len/2+1; p++) { X[p] = new int*[len/2+1]; for(int q= 0; q < sum+1; q++) X[p][q] = new int[sum+1]; } for(int i = 1; i <= len; i++) { int lim = min(i, len/2); for(int j = 1; j <= lim; j++) { for(int k = 1; k <= sum; k++) { X[i][j][k] = X[i-1][j][k]; if(k >= a[i-1]) { if(X[i][j][k] < X[i-1][j-1][k-a[i-1]] + a[i-1]) { X[i][j][k] = X[i-1][j-1][k-a[i-1]] + a[i-1]; } } } } } int result = X[len][len/2][sum]; //delete [][][]X; // 销毁空间这种做法是错误的,应该和申请空间方法一样 return result; }
注意:其中申请三维动态空间的方法,以及销毁的方法。
上面解法的空间复杂度为O(N^2Sum),而且用到的是三维空间,是可以进行优化的。
“
我们观察前面不含路径的伪代码可以看出,F[i][j][k]只与F[i-1][][]有关,这一点状态方程上也能反映出来。所以我们可以用二维数组来代替三维数组来达到降低空间复杂度的目的。但是怎么代替里面存有玄机,我们因为F[i][j][k]只与F[i-1][][]有关,所以我们用二维数组来代替的时候应该对F[i][j][k]的“j”维进行逆序遍历。为什么?因为只有这样才能保证计算F[i][j][k]时利用的F[i-1][j][]和F[i-1][j-1][]是真正i-1这个状态的值,如果正序遍历,那么当计算F[][j][]时,F[][j-1][]已经变化,那么计算的结果就是错误的。
伪代码如下
[cpp] view plaincopy
F[][]← 0
for i ← 1 to 2*N
nLimit ← min(i,N)
do for j ← nLimit to 1
do for k ← A[i] to Sum/2
if (F[j][k] < F[j-1][k-A[i]]+A[i])
then F[j][k] ← F[j-1][k-A[i]]+A[i]
return F[N][Sum/2] and Path[][][]
”
动态规划的题目写起来还是不顺额。。。
参考:
http://blog.csdn.net/wumuzi520/article/details/7028705
http://wenku.baidu.com/link?url=CQkQIsOqAYP41MuP9yavgklpHiST6BXXDi2zwfvGnRZEDHMF6S7LrwinwMPW3C6YAKkBw2i397aLNtppediKQ2nPJ4BSqFo8KUb0VbmA1Eu