题目说明:
给你一个下标从 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 下列等式均成立:
化简得:
错误方法①:贪心思想
1号k元组的和与2号k元组和比较,若大则1号k元组的第一位减一,否则1号k元组的第一位加一。这种方法固然可以达到最终效果,但肯定不是次数最少的方案。因为可能这两个元组都需要增加(即:1号元组的第一位增加,2号元组的最后一位增加),但是由于相比较使得1号元组平白无故减一。最终结果不正确。
错误方法②:对元组和求平均值,再修正。
贪心思想最大的问题就在于没有从全局的角度看待问题,对于一个需要最少or最大or类似精确的数字题目,贪心思想注定是不成功的。
我比赛时候写的第二个方法就是对所有的元组求平均值,求出平均和之后,再对每一个元组进行修正,然而问题很快就暴露出来,对于k元组中修正哪一个子数成为无法解决的难题。因为每修正一个数都会对另外的k-1个元组的和造成影响。无法保证修正的数是最优的数。
正解:(中位数贪心+裴蜀定理)
对于之前的分析,我们继续深入分析下去。
题目要求是对所有的k元组和都相等,那么等式可以继续往下扩展:
由于这是一个循环数组,所以我们还需对上式做出进一步处理:
而由于是k元组,这样的式子一共有k个(这里不理解的同学可以拿三元组手算一下),如下:
综上,我们只需要保证每一个例子,上诉等式成立即可。
由此我们可以获得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是没有用的变量。