数组的均值分割
给定你一个整数数组 nums
我们要将 nums 数组中的每个元素移动到 A 数组 或者 B 数组中,使得 A 数组和 B 数组不为空,并且 average(A) == average(B)
1. 折半查找+二进制枚举
首先将问题转化为求目标和为特定值
将所有数乘以n减去总和,转化成为求目标值为为0的数组,同时避免出现小数情况
class Solution {
public:
bool splitArraySameAverage(vector<int> &nums) {
int n = nums.size(), m = n / 2;
if (n == 1) return false;
int sum = accumulate(nums.begin(), nums.end(), 0);
for (int &x : nums)
x = x * n - sum;
//问题转化成了求目标和为0
//接下来使用折半搜索减小时间复杂度
unordered_set<int> left;//哈希记录所有组合的和
for (int i = 1; i < (1 << m); i++) {//遍历所有状态
int total = 0;
for (int j = 0; j < m; j++) {//计算当前状态和
if (i & (1 << j))
total += nums[j];
}
if (total == 0) return true; //该值为0,说明另一部分也为0
left.insert(total);//记录所有状态组合的和
}
//前半部分找不到和为0的组合,从后半部分找和为相反数的组合
int rsum = accumulate(nums.begin() + m, nums.end(), 0);//防止全部取完,这种情况下只存在一个目标和为0
for (int i = 1; i < (1 << (n - m)); i++) {//遍历剩下数的状态组合
int total = 0;
for (int j = m; j < n; j++) {//计算状态和
if (i & (1 << (j - m))) {
total += nums[j];
}
}
if (total == 0 || (rsum != total && left.count(-total))) //和为0,或者为前半部分组合的相反数(不能全部取完)
return true;
}
return false;
}
};
2. 动态规划(值维度优化)
类似求目标和和和零钱兑换的背包问题
对每k个数,使目标和为k*average
dp[i][x]表示用i个元素构成和x的可能性,再检查x==i*ave即可
这里我们只用检查到一半大小的元素组合即可,因为要存在两个满足条件的互补子数组
为了方便转移,值的维度使用集合,避免过于稀疏
class Solution {
public:
bool splitArraySameAverage(vector<int>& nums) {
int n = nums.size(), m = n / 2;
int sum = accumulate(nums.begin(), nums.end(), 0);
bool isPossible = false;
for (int i = 1; i <= m; i++) {//判断是否可能存在,n*subsum = i*sum ,故sum * i % n == 0
if (sum * i % n == 0) {
isPossible = true;
break;
}
}
if (!isPossible) return false;
vector<unordered_set<int>> dp(m + 1);//m个数能组合成的值
dp[0].insert(0);//边界条件,0组成成值0
for (int num: nums) {//遍历所有数
for (int i = m; i >= 1; i--) {//从后往前放置,保证只出现一次
for (int x: dp[i - 1]) {//由上一个状态转移
int curr = x + num;//计算和
if (curr * n == sum * i) return true; //满足条件直接剪枝
dp[i].emplace(curr);//放入当前数的和
}
}
}
return false;
}
};
3. 动态规划拓展(个数维度优化)
方法二中提到的未优化方法
使用二维数组(超时)
class Solution {
public:
bool splitArraySameAverage(vector<int>& nums) {
int n = nums.size();
int sum = accumulate(nums.begin(), nums.end(), 0);
vector<vector<bool>> dp(sum + 1, vector<bool>(n / 2 + 1));
dp[0][0] = true;
for (int num : nums)
for (int j = n/2; j > 0; j--) //可选数的数量,从后往前(其实两个维度中一个从后往前即可保证元素不会被重复选取)
for (int i = num; i <= sum; i++) //能组合成的值
dp[i][j] = dp[i][j]|dp[i - num][j - 1];
for (int i = 1; i <= n / 2; i++)
if (sum * i % n == 0 && dp[sum * i / n][i]) return true;
return false;
}
};
将个数维度用二进制位图优化,可以通过一次位运算(左移)完成所有状态转移
class Solution {
public:
bool splitArraySameAverage(vector<int>& nums) {
int n = nums.size();
int sum = accumulate(nums.begin(), nums.end(), 0);
// 优化:dp[s] 的第 i 位二进制位为 1 表示 dp[s][i] = true
vector<int> dp(sum + 1);
dp[0] = 1 << 0;
for (int num : nums)
for (int i = sum; i >= num; i--)
if (dp[i - num] != 0)
dp[i] |= (dp[i - num] << 1);
for (int i = 1; i <= n / 2; i++)
if (sum * i % n == 0 && (dp[sum * i / n] & (1 << i))) return true;
return false;
}
};