写好每一份题解,本博文源于胡凡的《算法笔记》,旨在解决分集合,给定一个由整数组成的集合,集合中的整数各不相同,现在要分为两个子集合,使得这两个子集合的并为原集合、交为空集,同时在两个子集合的元素个数n1与n2之差的绝对值 ∣ n 1 − n 2 ∣ |n_1-n_2| ∣n1−n2∣尽可能小的前提下,要求它们各自的元素之和 S 1 S_1 S1与 S 2 S_2 S2之差的绝对值 ∣ S 1 − S 2 ∣ |S_1-S_2| ∣S1−S2∣尽可能大,求这两个|S1-S2|等于多少。
最直接的思路是将原集合中的元素从小到大排序,取排序后的前n/2个元素作为其中一个子集合,剩下的元素作为另一个子集合即可。
还有一种就是随机选择,解决的方法是求原集合中的元素的第n/2大,同时根据这个数把集合分为两个部分,使得其中一个子集合都大于这个数,另一个都小于这个数。因此时间复杂度为O(n).
#include<cstdio> #include<cstdlib> #include<ctime> #include<algorithm> #include <cmath> using namespace std; const int maxn = 10010; int A[maxn],n; int randPartition(int A[],int left,int right) { //生成[left,right]内的随机数p int p = (int)(round(1.0*rand()/RAND_MAX*(right-left)+left)); int temp1 = A[p]; // 将A[left]存放至临时变量temp A[p] = A[left]; A[left] = temp1; int temp = A[left]; while(left<right){ //只要left与right不相遇 while(left<right && A[right] > temp) right--; // 反复左移right A[left] = A[right]; //将A[right]挪到 while(left<right && A[left] <= temp) left++; // 反复右移left A[right] = A[left]; } A[left] = temp; return left; } void randSelect(int A[],int left,int right,int K){ if(left == right) return ; //边界 int p = randPartition(A,left,right); //划分后主元的位置P int M = p - left + 1; // A[p]是A[left,right]中的第M大 if(K==M) return ; if(K<M){ //第k大的数在主元左测 randSelect(A,left,p-1,K);//划分后元素的位置为p }else{ //第K大的数在主元右侧 randSelect(A,p+1,right,K-M);//往主元右侧找第K-M大的数 } } int main(){ srand((unsigned)time(NULL)); //sum和sum1记录所有着呢概述 int sum = 0,sum1 = 0; scanf("%d",&n); for(int i = 0;i<n;i++){ scanf("%d",&A[i]); sum += A[i]; } randSelect(A,0,n-1,n/2); for(int i = 0;i<n/2;i++) sum1 += A[i]; //求两个子集合的元素和之差 printf("%d\n",(sum-sum1)-sum1); return 0; }