有一个无序、元素个数为2n的正整数数组,要求:如何能把这个数组分割为元素个数为n的两个数组,并使两个子数组的和最接近?
分析与解法
从题目中可以分析出,题目的本质就是要从2n个整数中找出n个,使得它们的和尽可能地靠近所有整数之和的一半。
解法一:不靠谱的解法
先将数组的所有元素排序,然后划分为S1 = {a1, a3, ..., a2n-1}和S2 = {a2, a4, ..., a2n};
从S1和S2中找出一对数进行交换,使得两个集合中所有元素的和之间的差值尽可能的小,直到找不到可对换的。
这种想法的缺陷是得到的S1和S2并一定总是最优的。
解法二:搜索
假设2n个数之和的一半为target。我们要从2n个整数中找出n个元素的和,有三种可能:大于target、等于target和小于target。其中,大于和小于target这两种情况没有本质区别。因此,可以只考虑小于target的情况,而大于target的情况用来搜索中进行剪枝。
用closestset来存放当前找到的最接近target的n个数,closestsum为这n个数的和。
当搜索到n个数的和小于target且大于closestsum,则替换当前的最优解,直到搜索完毕。参考代码如下:
1 #include <iostream> 2 #include <vector> 3 using namespace std; 4 5 const int maxn = 10007; 6 int arr[maxn]; 7 int n, target, closestsum; 8 vector<int> closestset; 9 10 void dfs(vector<int> path, int cur, int sum) 11 { 12 if (path.size() == n/2) 13 { 14 if (sum > closestsum) 15 { 16 closestset = path; 17 closestsum = sum; 18 } 19 return ; 20 } 21 if (cur == n) 22 { 23 return ; 24 } 25 for (int i = cur; i < n; i++) 26 { 27 sum += arr[cur]; 28 if (sum <= target) 29 { 30 path.push_back(arr[cur]); 31 dfs(path, i+1, sum); 32 sum -= arr[cur]; 33 path.pop_back(); 34 } 35 else 36 { 37 sum -= arr[cur]; 38 } 39 } 40 } 41 42 int main(int argc, char *argv[]) 43 { 44 while (cin >> n) 45 { 46 if (n % 2) 47 { 48 cout << "enter an even number please:\n"; 49 continue; 50 } 51 target = 0, closestsum = 0; 52 closestset.clear(); 53 for (int i = 0; i < n; i++) 54 { 55 cin >> arr[i]; 56 target += arr[i]; 57 } 58 target /= 2; 59 vector<int> path; 60 int sum = 0, cur = 0; 61 dfs(path, cur, sum); 62 cout << target << " " << closestsum << endl; 63 } 64 }
时间复杂度为O(2n)。
时间: 2024-10-06 23:20:39