🚩传送门:https://leetcode-cn.com/problems/minimum-moves-to-equal-array-elements/

题目

给定一个长度为 n 的 非空 整数数组,每次操作将会使 n - 1 个元素增加 1。找出让数组所有元素相等的最小操作次数。

示例:

输入:[1,2,3] 输出:3 解释: 只需要3次操作(注意每次操作会增加两个元素的值): [1,2,3] => [2,3,3] => [3,4,3] => [4,4,4]

解题思路:暴力法 [超时]

我们扫描整个数组以查找最大值和最小元素。此后,我们将 1 添加到除最大元素之外的所有元素,并增加移动数的计数。同样,我们重复相同的过程,直到最大元素和最小元素彼此相等。

复杂度分析

时间复杂度:,其中 是数组的长度, 为最大值和最小值的差。

空间复杂度:,不需要额外空间。

官方代码

  1. public class Solution {
  2. public int minMoves(int[] nums) {
  3. int min = 0, max = nums.length - 1, count = 0;
  4. while (true) {
  5. for (int i = 0; i < nums.length; i++) {
  6. //1. 查找最大值
  7. if (nums[max] < nums[i]) {
  8. max = i;
  9. }
  10. //2. 查找最小值
  11. if (nums[min] > nums[i]) {
  12. min = i;
  13. }
  14. }
  15. //3. 二者相等说明完成操作
  16. if (nums[max] == nums[min]) {
  17. break;
  18. }
  19. //4. 将非最大值集体加1
  20. for (int i = 0; i < nums.length; i++) {
  21. if (i != max) {
  22. nums[i]++;
  23. }
  24. }
  25. count++;
  26. }
  27. return count;
  28. }
  29. }


解题思路:改进暴力法 [超时]

上一方法中,每一步对每个元素增加 1。我们可以在一定程度上改进这一方法。为了让最小元素等于最大元素,至少需要加 k=max-min 次。在那之后,最大元素可能发生变化。因此,我们一次性增加增量 k,并将移动次数增加 k。然后,对整个数组进行扫描,找到新的最大值和最小值,重复这一过程直到最大元素和最小元素相等。

复杂度分析

时间复杂度:,其中 是数组的长度。

空间复杂度:,不需要额外空间。

官方代码

  1. public class Solution {
  2. public int minMoves(int[] nums) {
  3. int min = 0, max = nums.length - 1, count = 0;
  4. while (true) {
  5. for (int i = 0; i < nums.length; i++) {
  6. //1.找到最大值
  7. if (nums[max] < nums[i]) {
  8. max = i;
  9. }
  10. //2.找到最小值
  11. if (nums[min] > nums[i]) {
  12. min = i;
  13. }
  14. }
  15. //3.计算差值
  16. int diff = nums[max] - nums[min];
  17. if (diff == 0) {
  18. break;
  19. }
  20. //4.标记移动步数
  21. count += diff;
  22. //5.依次加上差值 k
  23. for (int i = 0; i < nums.length; i++) {
  24. if (i != max) {
  25. nums[i] = nums[i] + diff;
  26. }
  27. }
  28. }
  29. return count;
  30. }
  31. }

解题思路:利用排序 [通过]

如果对数组进行排序得到有序数列 a,可以有效地简化问题。类似于方法二,我们用 diff=max-min 更新数列。但不同的是,我们不需要每次都遍历整个数组来得到最大和最小值,而是可以利用数组的有序性在 O(1) 时间内找到更新后的最大值和最小值。此外,我们也不需要真的更新数组的值。

为了便于理解,下面逐步讲解该算法。

首先,假设我们在每一步计算 diff 之后正在更新有序数组的元素。下面展示如何在不遍历数组的情况下找到最大最小值。在第一步中,最后的元素即为最大值,因此 diff=a[n-1]-a[0]。我们对除了最后一个元素以外所有元素增加 diff

现在,更新后的数组开头元素 a'[0] 变成了 a[0]+diff=a[n-1]。因此,a'[0] 等于上一步中最大的元素 a[n-1]。由于数组排过序,直到 i-2 的元素都满足 a[j]>=a[j-1]。因此,更新之后,a'[n-2] 即为最大元素。而 a[0] 依然是最小元素。

于是,在第二次更新时,diff=a'[n-2]-a'[0]=(a[n-2]+diff)-(a[0]+diff)=a[n-2]-a[0]。更新后 a''[0] 会成为 a'[n-2],与上一次类似。然后,由于 a'[0]a'[n-1] 相等,在第二次更新后, a''[0]=a''[n-1]=a'[n-2]。于是,最大的元素为 a[n-3]

于是,我们可以继续这样,在每一步用最大最小值差更新数组。

下面进入第二步。第一步中,我们假设每一步会更新数组 a 中的元素。但事实上,我们不需要这么做。这是因为,即使是在更新元素之后,我们要登记的 diff 差值也不变,因为 maxmin 增加的数字相同。
于是,我们可以简单的将数组排序一次,

复杂度分析

时间复杂度:,其中 是数组的长度,排序需要的时间。

空间复杂度:,不需要额外空间。

官方代码

  1. public class Solution {
  2. public int minMoves(int[] nums) {
  3. Arrays.sort(nums);
  4. int count = 0;
  5. for (int i = nums.length - 1; i > 0; i--) {
  6. count += nums[i] - nums[0];
  7. }
  8. return count;
  9. }
  10. }