题目:
Given an array of 2n integers, your task is to group these integers into n pairs of integer, say (a1, b1), (a2, b2), …, (an, bn) which makes sum of min(ai, bi) for all i from 1 to n as large as possible.
Example 1:
Input: [1,4,3,2]
Output: 4
Explanation: n is 2, and the maximum sum of pairs is 4.
Note:
n is a positive integer, which is in the range of [1, 10000].
All the integers in the array will be in the range of [-10000, 10000].
思路:
就是给2n个数分组,两两一组,使得每一组中最小的那个数的和最大。这个问题其实很有趣,最终我们要做的事情就是选出来的的数字是两个数中最小的,但却要尽可能的大,这样才能使得n个数字求和之后得到的结果最大。不妨这样想,先把这些数字数字排序,得到由小到大的有序数组,从最大的数字挑起,最大的数字显然最后不会参与求和,那它该挑谁走才能为最后的加和贡献一个大的数字呢?显然是第二大的数;由此类推,第三大应该和第四大数字配对,也就是说对有序数组两两分组即可。问题的关键变为选择高效的排序算法。
关于为何要这么选数字,严格证明如下:
- 假设对于每一对i,bi >= ai。
- 定义Sm = min(a1,b1)+ min(a2,b2)+ … + min(an,bn)。最大的Sm是这个问题的答案。由于bi >= ai,Sm = a1 + a2 + … + an。
- 定义Sa = a1 + b1 + a2 + b2 + … + an + bn。对于给定的输入,Sa是常数。
- 定义di = | ai - bi |。由于bi >= ai,di = bi-ai, bi = ai+di。
- 定义Sd = d1 + d2 + … + dn。
- 所以Sa = a1 + (a1 + d1) + a2 + (a2 + d2) + … + an + (an + di) = 2Sm + Sd , 所以Sm =(Sa-Sd)/ 2。为得到最大Sm,给定Sa为常数,需要使Sd尽可能小。
- 所以这个问题就是在数组中找到使di(ai和bi之间的距离)的和尽可能小的对。显然,相邻元素的这些距离之和是最小的
排序问题是个老生常谈的问题,我最开始用的冒泡排序,但是最后的4组测试数据没有通过,报错超时,因为O(n^2)的复杂度处理较长序列时是很费时间的,但是冒泡排序作经典的排序算法,应当掌握,代码如下:
1 class Solution { 2 public: 3 int arrayPairSum(vector<int>& nums) { 4 int temp; 5 for(int i=1;i<nums.size();i++) 6 { 7 for(int j=0;j<nums.size()-i;j++) 8 { 9 if(nums[j]>nums[j+1]) 10 { 11 temp = nums[j]; 12 nums[j]=nums[j+1]; 13 nums[j+1]=temp; 14 } 15 } 16 } 17 int sum=0; 18 for(int i=0;i<nums.size();i=i+2) 19 sum+=nums[i]; 20 return sum; 21 } 22 };
于是我改用快速排序,但是让人伤心的是,最后还是报超时,81组测试数组我只通过了79组,后来仔细一想,肯定是函数调用以及递归的过程花费了太多时间
程序如下:
1 class Solution { 2 public: 3 int arrayPairSum(vector<int>& nums) { 4 int sum = 0; 5 Qsort(nums, 0, nums.size() - 1); 6 for (int i = 0; i < nums.size(); i++) 7 cout << nums[i] << " "; 8 cout << endl; 9 for (int i = 0; i < nums.size(); i = i + 2) 10 sum += nums[i]; 11 return sum; 12 } 13 void Qsort(vector<int> &nums, int low, int high) 14 { 15 if (low < high) 16 { 17 int first = low; 18 int last = high; 19 int key = nums[first]; 20 while (first < last) 21 { 22 while (first<last&&nums[last] >= key) 23 last--; 24 nums[first] = nums[last]; 25 while (first<last&&nums[first] <= key) 26 first++; 27 nums[last] = nums[first]; 28 } 29 nums[first] = key; 30 Qsort(nums, low, first - 1); 31 Qsort(nums, first + 1, high); 32 } 33 34 } 35 };
但是最优解应当是使用STL模板中的sort()函数,直接通过所有测试数据,代码简单。时间复杂度O(n*logn),这已经是排序算法中最优的复杂度了,但是在面试过程中是否允许使用STL应当征求面试官的意见。sort()函数的使用参见 http://www.cplusplus.com/reference/algorithm/sort/?kw=sort
代码如下:
1 class Solution { 2 public: 3 int arrayPairSum(vector<int>& nums) { 4 sort (nums.begin(), nums.end()); 5 int sum=0; 6 for(int i=0;i<nums.size();i=i+2) 7 sum+=nums[i]; 8 return sum; 9 10 } 11 };
最后再补充一种在讨论区看见的大神的写法,复杂度直接降低为O(n),叫做“桶排序Bucket sort”,代码如下:
1 class Solution { 2 public: 3 int arrayPairSum(vector<int>& nums) { 4 vector<int> hashtable(20001,0); 5 for(size_t i=0;i<nums.size();i++) 6 { 7 hashtable[nums[i]+10000]++; 8 } 9 int ret=0; 10 int flag=0; 11 for(size_t i=0;i<20001;){ 12 if((hashtable[i]>0)&&(flag==0)){ 13 ret=ret+i-10000; 14 flag=1; 15 hashtable[i]--; 16 }else if((hashtable[i]>0)&&(flag==1)){ 17 hashtable[i]--; 18 flag=0; 19 }else i++; 20 } 21 return ret; 22 } 23 };
这种方法的精妙之处在于把一个数组中数字的大小和另一个数组中元素的脚标建立了联系;缺点就是空间复杂度太高。算法真的是一门艺术!