最小操作次数使数组元素相等 图解题解
这道题到底在问什么
- 输入
- nums=[1,2,3]
- 输出
- 3 ([1,2,3]→[2,3,3]→[3,4,3]→[4,4,4])
- 输入
- nums=[1,1,1]
- 输出
- 0 (本来就相等,不用操作)
先想最直接的笨办法
扫完了,最小值是绿色这个 1。接下来每个数都要减到 1,灰色这些就是要往下降的。一个一个算它们各要减几次。(动画第 12 步)
最优解:一步一步想明白
- 3记住这条转化:加 n-1 个 ≡ 减 1 个。基准是「最小值」,答案 = 总和减去 n 乘最小值。下面逐帧套它。
- 4先把数组摆出来:4、1、3、2、5、2,一共 6 个数,和是 17。直接想「往上加」会乱,换个角度看。
- 5核心转化:给 n-1 个各加 1,等于把剩下那个相对减 1。只看相对差,就把「加」翻译成了「减」。那大家都减到谁?减到最小的那个最省。
- 6先扫一遍找最小值。第 0 个是 4,暂定它是最小,绿色标住。
- 7第 1 个是 1,比当前最小 4 还小,最小值更新成 1,绿色挪到这里。
- 8第 2 个是 3,不小于当前最小 1,最小值不变,绿色仍在下标 1。
- 9第 3 个是 2,不小于当前最小 1,最小值不变,绿色仍在下标 1。
- 10第 4 个是 5,不小于当前最小 1,最小值不变,绿色仍在下标 1。
- 11第 5 个是 2,不小于当前最小 1,最小值不变,绿色仍在下标 1。
- 12扫完了,最小值是绿色这个 1。接下来每个数都要减到 1,灰色这些就是要往下降的。一个一个算它们各要减几次。
- 13轮到下标 0 的 4。它要降到 1,得减 4 − 1 = 3 次。
- 14把这 3 次加进总数,累计操作变成 3。下标 0 结算完,变绿。
- 15轮到下标 1 的 1。它要降到 1,得减 1 − 1 = 0 次,它本来就是最小,一次都不用减。
- 16把这 0 次加进总数,累计操作变成 3。下标 1 结算完,变绿。
- 17轮到下标 2 的 3。它要降到 1,得减 3 − 1 = 2 次。
- 18把这 2 次加进总数,累计操作变成 5。下标 2 结算完,变绿。
- 19轮到下标 3 的 2。它要降到 1,得减 2 − 1 = 1 次。
- 20把这 1 次加进总数,累计操作变成 6。下标 3 结算完,变绿。
- 21轮到下标 4 的 5。它要降到 1,得减 5 − 1 = 4 次。
- 22把这 4 次加进总数,累计操作变成 10。下标 4 结算完,变绿。
- 23轮到下标 5 的 2。它要降到 1,得减 2 − 1 = 1 次。
- 24把这 1 次加进总数,累计操作变成 11。下标 5 结算完,变绿。
- 25每个元素要减的次数加起来,正好等于 sum 减去 n 乘 min:17 − 6×1 = 11。所以最少操作 11 次,全员降到 1。
⚠️ 容易写错的地方
✗ 错:真去循环模拟「每次给 n-1 个加 1」
✓ 对:套公式 sum - n×min 直接出
数值可达 10^9 量级,逐次模拟会超时
✗ 错:基准选成最大值(让大家升到 max)
✓ 对:基准是最小值 min
等价成「减 1」后,降到最小才最省,升到 max 反而更多步
✗ 错:用 32 位 int 累加 sum、算 n×min 导致中间值溢出
✓ 对:累加和与中间乘积都用足够大的整型(C++ long long、Java asLongStream、Python 天然大整数)
本题 n 到 10^5、元素到 10^9,sum 与 n×min 都会超过 32 位 int 的范围,溢出会算出错误答案;最终结果保证落在 int 范围,再转回 int 返回
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class Solution:
def minMoves(self, nums: List[int]) -> int:
return sum(nums) - min(nums) * len(nums)C++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
int minMoves(vector<int>& nums) {
long long s = 0;
int mi = 1 << 30;
for (int x : nums) {
s += x;
mi = min(mi, x);
}
return (int)(s - 1LL * mi * (long long)nums.size());
}
};Java
import java.util.*;
class Solution {
public int minMoves(int[] nums) {
long sum = Arrays.stream(nums).asLongStream().sum();
int mi = Arrays.stream(nums).min().getAsInt();
return (int) (sum - (long) mi * nums.length);
}
}复杂度
时间
O(n)
求 sum 和 min 各扫一遍,常数遍线性
空间
O(1)
只用 sum、min 两个变量
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 最小操作次数使数组元素相等 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么基准非得是最小值,不能是别的值?+
转化成「每次减 1」后,要让所有数相等,目标值不能高于当前最小值(最小值没法往上抬,只能别人往下降到它),也没必要降到比 min 更低(那要给所有数额外多减,操作只增不减)。所以降到 min 恰好最省,总步数 sum - n×min。
这题和 LC462「最少移动次数使数组元素相等 II」有什么区别?+
LC462 每次只能把某一个元素加 1 或减 1,目标是绝对距离最小,答案用中位数。本题操作形态固定(每次 n-1 个加 1),等价成统一减到最小值,基准是 min 不是中位数。操作规则不同,基准也不同。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 最小操作次数使数组元素相等 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。