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=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节,数组分割