题目描述:
存在一个长度为 n 的数组 arr ,其中 arr[i] = (2 * i) + 1 ( 0 <= i < n )。
一次操作中,你可以选出两个下标,记作 x 和 y ( 0 <= x, y < n )并使 arr[x] 减去 1 、arr[y] 加上 1 (即 arr[x] -=1 且 arr[y] += 1 )。最终的目标是使数组中的所有元素都 相等 。题目测试用例将会 保证 :在执行若干步操作后,数组中的所有元素最终可以全部相等。
给你一个整数 n,即数组的长度。请你返回使数组 arr 中所有元素相等所需的 最小操作数 。
示例 1:
输入:n = 3
输出:2
解释:arr = [1, 3, 5]
第一次操作选出 x = 2 和 y = 0,使数组变为 [2, 3, 4]
第二次操作继续选出 x = 2 和 y = 0,数组将会变成 [3, 3, 3]
示例 2:
输入:n = 6
输出:9
提示:
1 <= n <= 10^4
解题:
1.直接计算
class Solution { public: /* 目的:将数组中的数都变为中位数需要进行交换的次数 当 n : 1.n == 1:cout<<0; 2.为奇数时,从(n-1)/2向外扩展 i++,j--,累加到 l = (arr[i] - arr[j])/2; 3.为偶数时,从中间 i = n / 2; j = ( n - 2 ) / 2; 累加到 l = (arr[i] - arr[j])/2; */ int minOperations(int n) { if(n == 1)return 0; vector<int> arr(n,0); //创建arr空间为n,并初始化为0 unsigned int i,l = 0,j; //将arr[i] == (2*i)+1进行保存 for(i = 0; i < n ; i ++) { arr[i] = ( 2 * i ) + 1 ; } //奇数操作 if(n % 2 == 1) for(i = ( n-1 )/2, j = i; i < n ; i++ , j--)//j 到达 0 的同时 i 到达 n-1 { l += (arr[i]-arr[j])/2; } else //偶数操作 for(i = n/2,j = (n-2)/2; i < n ; i++ , j--) { l += (arr[i]-arr[j])/2; //cout <<l<<" " ; } return l; } };
时间复杂度o(n),空间复杂度o(1)
2.
class Solution { public: int minOperations(int n) { //初始化 vector<int> a(n); int sum = 0; for (int i = 0; i < n; ++i) { //计算数据 a[i] = 2*i+1; //计算总和 sum += a[i]; } //计算平均 sum /= n; int ans = 0; for (int i = 0; i < n; ++i) { //计算操作次数x2 ans += abs(sum-a[i]); } return ans/2; } };
时间复杂度o(n),空间复杂度o(1)
3.取巧,从数据中找规律
(本方法借鉴于力扣)作者:zyoung1024
链接:https://leetcode-cn.com/problems/minimum-operations-to-make-array-equal/solution/deng-chai-shu-lie-mo-ni-he-shu-xue-fang-fa-xiang-j/
(1)
拿到这道题,感觉有点绕,仔细分析发现arr[i] = (2 * i) + 1 (0 <= i < n)是典型的等差数列(1,3,5,7,9...).
根据等差数列的求和公式,很容易求出数组arr的元素总和是n^2.
题设中说每次操作选出两个下标x y使arr[x]减一arr[y]加一.换句话说,无论怎样选择x y,无论操作多少次,数组的总和不会变.
题设又保证数组中所有元素最终可以全部相等.
那我们假设最终所有元素等于a那么n*a == n^2,所以a == n,也就是说最终数组元素都是n.其实n是数组的平均值.知道最终元素都是n后,通过从数组起始和末尾下标开始向中间遍历,就可以到达操作数最小.
假设左边的下标是i ((2 * i) + 1 < n)那么相应右边的下标是n - i.相应两个元素值与n的差都是n - 1 + 2 * i.所以我们只要计算数组中值小于n的元素与n的差的总和,就得到最小操作数了.
int minOperations(int n) { int operation = 0; for(int i = 1; i < n ; i += 2) { operation += (n - i); } return operation; } // 时间复杂度是O(n) 空间复杂度是O(1)
(2)
因为是等差数列,很可能找到一个数学公式,用O(1)的时间复杂度解决.
先举几个简单的例子找找规律
n=3 最小操作数是 2
n=4 最小操作数是 1 + 2
n=5 最小操作数是 2 + 4
n=6 最小操作数是 1 + 3 + 5
n=7 最小操作数是 2 + 4 + 6
果然有规律:
当n是偶数的时候,最小操作数是1 + 3 + 5 + ... + n-1 = n*n/4
当n是奇数的时候,最小操作数是2 + 4 + ... + n-1 = (n*n - 1) / 4
注意: 上面的求和公式都是数学形式
那能不能再简单一点呢? 如果用整除代替数学中的除法,可以将(n*n - 1) / 4修改成n*n/4,因为1整除4为0不影响最后的结果.
所以有了下面的代码,是不是很酷 :)
int minOperations(int n) { reutrn n * n / 4; } // 时空复杂度都是 O(1)