数组的均值分割

给定你一个整数数组 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;
    }
};
posted @ 2023-06-04 17:56  失控D大白兔  阅读(38)  评论(0编辑  收藏  举报