使子数组元素和相等(中位数贪心+裴蜀定理)超详细

题目说明:

        给你一个下标从 0 开始的整数数组 arr 和一个整数 k 。数组 arr 是一个循环数组。换句话说,数组中的最后一个元素的下一个元素是数组中的第一个元素,数组中第一个元素的前一个元素是数组中的最后一个元素。

你可以执行下述运算任意次:

  •         选中 arr 中任意一个元素,并使其值加上 1 或减去 1 。

执行运算使每个长度为 k 的 子数组 的元素总和都相等,返回所需要的最少运算次数。

子数组 是数组的一个连续部分。

示例:

输入:arr = [1,4,1,3], k = 2
输出:1
解释:在下标为 1 的元素那里执行一次运算,使其等于 3 。
执行运算后,数组变为 [1,3,1,3] 。
- 0 处起始的子数组为 [1, 3] ,元素总和为 4
- 1 处起始的子数组为 [3, 1] ,元素总和为 4
- 2 处起始的子数组为 [1, 3] ,元素总和为 4
- 3 处起始的子数组为 [3, 1] ,元素总和为 4 

分析过程:

该题乍一看,极为容易陷入贪心的怪圈,比赛时也没时间细想,结果开始debug+坐牢。

这道题最关键在于使得每一个 i 下列等式均成立:

arr[i]+arr[i+1]+...+arr[i+k-1]==arr[i+1]+arr[i+2]+...+arr[i+k]

化简得:

arr[i]=arr[i+k]

错误方法①:贪心思想

1号k元组的和与2号k元组和比较,若大则1号k元组的第一位减一,否则1号k元组的第一位加一。这种方法固然可以达到最终效果,但肯定不是次数最少的方案。因为可能这两个元组都需要增加(即:1号元组的第一位增加,2号元组的最后一位增加),但是由于相比较使得1号元组平白无故减一。最终结果不正确。

错误方法②:对元组和求平均值,再修正。

贪心思想最大的问题就在于没有从全局的角度看待问题,对于一个需要最少or最大or类似精确的数字题目,贪心思想注定是不成功的。

我比赛时候写的第二个方法就是对所有的元组求平均值,求出平均和之后,再对每一个元组进行修正,然而问题很快就暴露出来,对于k元组中修正哪一个子数成为无法解决的难题。因为每修正一个数都会对另外的k-1个元组的和造成影响。无法保证修正的数是最优的数。

正解:(中位数贪心+裴蜀定理)

对于之前的分析,我们继续深入分析下去。

题目要求是对所有的k元组和都相等,那么等式可以继续往下扩展:

arr[i]=arr[i+k]=arr[i+2k]=...=arr[i+mk]=...

由于这是一个循环数组,所以我们还需对上式做出进一步处理:

arr[i]=arr[(i+k)%n]=arr[(i+2k)%n]=...=arr[(i+mk)%n]=...

而由于是k元组,这样的式子一共有k个(这里不理解的同学可以拿三元组手算一下),如下:

\left\{\begin{matrix} arr[i]=arr[(i+k)%n]=arr[(i+2k)%n]=...=arr[(i+mk)%n]=... \\ arr[i+1]=arr[(i+1+k)%n]=arr[(i+1+2k)%n]=...=arr[(i+1+mk)%n]=... \\ ...... \\ arr[i+k-1]=arr[(i+2k-1)%n]=arr[(i+3k-1)%n]=...=arr[(i+(m+1)k-1)%n]=... \end{matrix}\right.

综上,我们只需要保证每一个例子,上诉等式成立即可。

        由此我们可以获得k个数组,我们记为 vector<vector<int>> b 。

        对于每一个数组 b[i] 我们需要保证所有的数均相等,且修改数值总和最少。

中位数定理:

我们采用中位数作为基准,所有数向中位数靠齐,修改数值之和是最小的

证明:

假设有一串已经排好序的数组B,范围从0~m。从最外的一对数字开始(即B[0]、B[m])对于两位数,要使得修改数值最小,很明显需要在B[0]、B[m]之间,最小数值为:B[m]-B[0] 。此时,逐一往内判断,最终就会达到中位数这样的位置。

代码过程:

1. 将原数组按照公式分为k个子数组

2. 对于每一个子数组进行排序,并获取中位数。然后每一项求差,并对所有的差 求和。

3.  对每一个子数组的和求和并返回。

进一步优化:

事实上,对于长度为n的数组,k为间隔求和,若真的分为k个数组,实际上会出现重复操作。

例如对于数组 [1,4,1,3,5] k=2 。

会形成:

①1,1,5,4,3

②4,3,1,1,5

两个本质上一模一样的数组。那么此时运行速度就变慢了。

产生原因:

主要原因是循环数组,不仅对数组长度n循环,还对k循环。

如何不进入n的循环成为优化的关键。

方案:最大公约数。

显然我们不需要按照k进行循环,只需要按照k和n的最大公约数为公差进行访问数组即可,这样不需要重复进入n的循环,在一次遍历就可以把所有所需要的数都访问到,例如上一个举的式子,5和2的最大公约数为 1 。意味着每一个数都需要放入数组,此时所需的数组个数也只有1个。

最终代码过程:

1. 对原数组的长度n和k求最大公约数len

1. 将原数组按照公式分为len个子数组,当然此时公差也需要变更为len

2. 对于每一个子数组进行排序,并获取中位数。然后每一项求差,并对所有的差 求和。

3.  对每一个子数组的和求和并返回。

代码示例:

class Solution {
public:
    long long sumVector(vector<int> myVector){
        long long sum=0;
        nth_element(myVector.begin(),myVector.begin() + myVector.size() / 2, myVector.end());
        for(int i=0;i<myVector.size();i++){
            sum+=abs(myVector[i]-myVector[myVector.size()/2]);
        }
        return sum;
    }
    long long makeSubKSumEqual(vector<int>& arr, int k) {
        if(arr.size()==1) return 0;
        long long ans = 0;
        int len = __gcd(arr.size(),k%arr.size());
        for(int i=0;i<len;i++){
            vector<int> myVector;
            vector<int> flag;
            for(int j=i;j<arr.size();j=j+len){
                myVector.push_back(arr[j]);
            }
            ans+=sumVector(myVector); 
        }
        return ans;
    }
};

注:代码中的 vector<vector<int>> flag是没有用的变量。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值