开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 20 天,点击查看活动详情
Day49 2023/02/23
难度:简单
题目
已知由n(n>=2)个正整数构成的集合A ,将其划分成两个不相交的子集A1和A2,元素个数分别为n1和n2,A1和A2中元素之和分别为S1和S2。设计一个尽可能高效的划分算法,满足|n1-n2|最小且|S1-S2|最大。要求:
(1)给出算法的基本设计思想。
(2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
(3)说明你所设计算法的平均时间复杂度和空间复杂度。
示例
输入:a = {2, 6, 9, 8, 5}
输出:16
说明:若干划分后a变成了降序序列 {2,5,6,8,9} ,故差值为(2+5)- (6+8+9)的绝对值为16
思路
由题意知,将最小的n/2(向下取整)个元素放在A1中,其余的元素放在A2中,分组的结果即可满足题目要求。仿照快速排序的思想,基于枢轴将n个整数划分为两个子集。根据划分后枢轴所处的位置i分别处理
具体步骤:
- 若k=n/2(向下取整),则分组结束,算法结束
- 若k<n/2(向下取整),则枢轴及之前的所有元素均属于A1,继续对i之后的元素进行划分。
- 若k>n/2(向下取整),则枢轴及之后的所有元素均属于A2,继续对i之前的元素进行划分。
关键点
- 实际if判断中枢轴是与k-1进行比较,是因为初始时
k=n/2这是位序,而划分函数里面传入的参数是数组下标,所以用k-1统一成数组下标
算法实现
c++代码实现-枢轴划分法
#include <cmath>
#include <iostream>
using namespace std;
/**
* @function 将数组划分成左右2表,左边所有元素都小于枢轴值,右边都大于枢轴值
* @param a 整型数组 待划分集合
* @param low 整型 划分时的左边界
* @param high 整型 划分时的右边界
*/
void Partition(int a[],int& low, int& high) {
int pivot = a[low]; // 将当前表中第一个元素设为枢轴,对表进行划分
while (low < high) {
while (low < high && a[high] >= pivot) high--;
a[low] = a[high]; // 将比枢轴小的元素移动到它的左边
while (low < high && a[low] <= pivot) low++;
a[high] = a[low]; // 将比枢轴大的元素移动到它的右边
}
a[low] = pivot; // 枢轴元素存放到最终位置
}
/**
* @function 题目要求的划分函数
* @param a 整型数组 待划分数组
* @param n 整型 数组长度
* @return 整型 返回s1与s2差值的绝对值
*/
int setPartition(int a[], int n) {
int flag = 1, low = 0, high = n - 1, k = n / 2; // 判断标志,左边界,右边界,枢轴(位序)
int s1= 0, s2 = 0; // 划分后的两个数组的累计值
while (flag) {
Partition(a, low, high); // 通过枢轴划分区间
if (low == k - 1) flag = 0; // 枢轴位置处于集合中间位置,结束循环
else if (low < k - 1) { // 枢轴小于中心位置,继续向右划分寻找
low += 1; // 跟新左右边界
high = n - 1;
}
else {
low = 0; //// 跟新划分左右边界
high -= 1;
}
}
// 统计s1和s2
for (int i = 0; i < k; i++) s1 += a[i];
for (int i = k; i < n; i++) s2 += a[i];
return abs(s1 - s2); // 返回最大差值
}
// 测试一下
int main() {
int a[5] = {2, 6, 9, 8, 5};
int res = setPartition(a, sizeof(a)/4);
cout << "将集合划分后s1和s2的差值为:"<< res;
}
- 时间复杂度 --- 平均时间复杂度
- 空间复杂度 --- 无额外的辅助空间
总结
- 对划分集合这部分不清楚的同学可以去了解一下快速排序的的算法思想和具体代码。