编程之美-2.18-数组分割

1. 简述

    有一个无序、元素个数为2n的正整数数组,要求:如何能把这个数组分割为元素个数为n的两个数组,并使两个子数组的和最接近?例如有如下数组,1,5,7,8,9,6,3,11,20,17。应该分割为1,3,11,8,20和5,7,9,6,17。

2. 思路

    方法一,暴力搜索,遍历每种分组方法,一共C(2N, N)种组合,复杂度是(2N!/N!),复杂度过高。

    方法二,动态规划,原题是要求求两个数组的和最近接,这等价于要求其中较小的和最接近与2n个正整数的和(设为SUM)的一半。因此,弱化题目,求这个最近接一半的且小于等于SUM/2数值,定义Heap[i],i<=N表示任意i个数能够构成的数值集合。初始化:Heap[0]= 0。更新代码:

for(int i=1; i<2*N; i++) { // 依次读取A[i]更新堆 
for(int j=min{i, N}; j>0; j--) // 更新引入A[i]后可能的元素个数的情况
for each v in Heap[j-1] // 对于引入A[i]的情况
insert(Heap[j], A[i]+v);
}

    关于insert次数,至多为2^(N-1),为什么是这个数我没想明白,总共感觉上应该是C(2N,N)次插入啊。

    方法三,复杂度主要是由于堆很大,原因是我们记录的是各种可能组合出的数值。如果SUM值不高,可以定义bool FLAG[i][j],i=0,1,...,2*N,j=0,1,...,SUM/2,表示是否存在i个数,其和为j。初始化,FLAG[0][0] = true。

for(int k=1; k<2*N; k++) {
for(int i=min{k, N}; i>0; i--) { // 每一个可能的长度
for(int v=1; v<SUM/2; v++) { // 每一个可能的可能的数值
if(v>=A[k] && FLAG[i-1][v-A[k]])
FLAG[i-1][v-A[k]] = true;
}
}
}

    Max{j},其中,j=0,1,...,2*N,FLAG[N][j]=true,这即为所求。这样复杂度为O(N*N*SUM)级别的,尤其是当SUM相对不大的时候,复杂度会大大降低。

3. 参考

    编程之美,2.18节,数组分割

posted @ 2011-10-10 13:08  xiaodongrush  阅读(2101)  评论(0编辑  收藏  举报