• 805.数组的均值分割(回溯+折半搜索+数学)


    805. 数组的均值分割

    给定你一个整数数组 nums

    我们要将 nums 数组中的每个元素移动到 A 数组 或者 B 数组中,使得 A 数组和 B 数组不为空,并且 average(A) == average(B) 。

    如果可以完成则返回true , 否则返回 false  。

    注意:对于数组 arr ,  average(arr) 是 arr 的所有元素的和除以 arr 长度。

    示例 1:

    输入: nums = [1,2,3,4,5,6,7,8]
    输出: true
    解释: 我们可以将数组分割为 [1,4,5,8] 和 [2,3,6,7], 他们的平均值都是4.5。
    

    示例 2:

    输入: nums = [3,1]
    输出: false
    
    

    提示:

    • 1 <= nums.length <= 30
    • 0 <= nums[i] <= 104

    思路: 回溯+折半搜索+数学(力扣官解方法一)

    数组的均值分割 - 数组的均值分割 - 力扣(LeetCode)

    设数组 nums 的长度为 n,假设我们移动了 k 个元素到数组 A 中,移动了 n−k 个元素到数组 B 中,此时两个数组的平均值相等。设sum(A),sum(B),sum(nums) 分别表示数组 A,B,nums 的元素和,由于数组A,B 的平均值相等,则有 sum(A) / k = sum(B) / (n - k)​
     上式可以进行如下变换:
    sum(A)×(n−k)=sum(B)×k
    sum(A)×n=(sum(A)+sum(B))×k
    sum(A)×n=sum(nums)×k
    sum(A)/k=sum(nums)/n

    即数组 A 的平均值与数组 nums 的平均值相等。此时我们可以将数组 nums 中的每个元素减去 nums 的平均值,这样数组 nums 的平均值则变为 0。因为数组平均值为0,所以数组之和也为0,故此时题目中的问题则可以变为:从 nums 中找出若干个元素组成集合 A,使得 A 的元素之和为 0,剩下的元素组成集合 B,它们的和也同样为 0

    比较容易想到的思路是,从 n 个元素中取出若干个元素形成不同的子集,则此时一共有 2^n
    种方法(即每个元素取或不取),我们依次判断每种方法选出的元素之和是否为 0。但题目中的 n 可以达到 30,此时 2^30 =1,073,741,824,组合的数据非常大。

    因此我们考虑将数组平均分成两部分 A和 B,它们均含有 n/2个元素,此时这两个数组分别有 2^(n/2)​ 种不同的子集选择的方法,设 A中所有选择的方法得到的不同子集的元素之和的集合为 left,那么若在B中得到了一个子集之和x,则只需在left 中找到一个 -x 即可,此时用A、B的子集构成的集合元素之和就是 0

    将数组nums 中每个元素减去它们的平均值,这一步会引入浮点数。所以先将 nums 中的每个元素乘以 nums 的长度 n,则此时每个元素 nums[i] 变为 n×nums[i],这样数组 nums 的平均值就一定为整数。

    1. class Solution {
    2. public:
    3. unordered_map<int,int> left;//<左半数组k个数的组合之和,k>
    4. bool backtrack(vector<int>& nums, int start, int end, int sum,int cnt)
    5. {
    6. if (start >= end)//越界
    7. return false;
    8. if (sum == 0 && cnt != 0)//组合之和为0且组合不为空
    9. return true;
    10. //可以在left中找到右半数组的组合之和sum的相反数,两者结合的集合和为0
    11. //但要注意,两者结合的集合不能把nums中的所有元素都选掉,这样另一个集合就为空了
    12. if (start >= nums.size() / 2 && left.count(-sum) && left[-sum] + cnt != nums.size())
    13. return true;
    14. for (int i = start; i < end; i++)//选择列表
    15. {
    16. sum += nums[i];//做选择
    17. cnt++;
    18. if(start < nums.size() / 2)//将左半数组k个数的组合之和及k加入left中
    19. left.emplace(sum,cnt);
    20. if (backtrack(nums, i + 1, end, sum, cnt))//进入下一层决策树
    21. return true;
    22. sum -= nums[i];//取消选择
    23. cnt--;
    24. }
    25. return false;
    26. }
    27. bool splitArraySameAverage(vector<int>& nums) {
    28. int n = nums.size();
    29. int sum = accumulate(nums.begin(), nums.end(), 0);
    30. //nums[i]*n使数组平均值也乘n,这样数组平均值就一定不是浮点数
    31. //nums[i]*n-sum,使数组平均值为0,这样数组之和也为0,只要找到一个子数组之和也为0就行了
    32. for (int& i : nums)
    33. {
    34. i = i * n - sum;
    35. }
    36. if (backtrack(nums, 0, n / 2, 0, 0))//左半数组的所有元素的组合之和
    37. return true;
    38. if (backtrack(nums, n / 2, n, 0, 0))//右半数组的所有元素之和
    39. return true;
    40. return false;
    41. }
    42. };
  • 相关阅读:
    如何套用模板制作大屏?
    Windows使用小技巧
    vue学习-14vue的路由缓存组件以及activated和deactivated生命周期钩子
    2023年面试测试工程师一般问什么问题?
    【C++】内联函数的原理及使用
    五个步骤轻松搞定软件开发流程
    深入高性能NIO框架,Netty权威详解,智能时代构建高可用系统利器
    EPICS asynPortDriver中数组用法示例
    2021年亚太杯APMCM数学建模大赛C题生态保护的建设及其对环境影响的评价求解全过程文档及程序
    故障诊断模型 | Maltab实现RF随机森林的故障诊断
  • 原文地址:https://blog.csdn.net/weixin_50437588/article/details/127875967