题目描述
思路解析
一句话答案:LeetCode 453 最小操作次数使数组元素相等:每次给 n−1 个数加 1 等价于给 1 个数减 1,把所有数减到最小值即可,答案 = sum − n×min,时间 O(n)、空间 O(1)。
每次给 n−1 个数加 1,最少几次能让全员相等
给一个长度为 n 的整数数组 nums,一次操作要挑其中 n−1 个元素、各加 1(只留一个数不动),问最少操作几次能让所有元素相等。题面例子 nums=[1,2,3] 答案是 3,nums=[1,1,1] 本来就齐,答案 0。数据不小,n 到 10^5、每个数到 10^9。
照着「每次挑 n−1 个加 1」硬模拟,会卡在哪
按字面模拟:每轮把当前最大的那个留住不动、其余全体加 1,直到齐平。可答案本身就能到 10^9 量级——[1,2,3] 才 3 次,换成几个相差上亿的数,就得循环上亿轮、每轮还要扫一遍数组,稳稳超时。更别扭的是盯着「往上加」,数越加越大,谁也说不清要加到多高才停。
把「加 n−1 个」翻译成「减 1 个」
让一堆数相等,真正在意的只是它们之间的相对差——谁比谁高出多少。给 n−1 个数各加 1,那个没被加到的数,相对大家就矮了 1,这和「单独把它减 1」的效果一模一样。于是每次操作都能等价看成「挑一个数减 1」。
视角一翻转,目标就从「一起往上加到某个高度」变成「各自往下减到某个高度」。而往下减,大家减到最小值 min 就到头了:min 本身没法再降(想让它更低,反而要给别人多减),比它高的数往下够到它就行。
减到最小值,答案就落成 sum − n×min
既然每次减 1、都往 min 靠,那第 i 个数 nums[i] 要减的次数,就是它高出 min 的那部分 nums[i] − min。把每个数各自要减的次数加起来,就是总操作数:(nums[0]−min) + (nums[1]−min) + … 一共 n 项,每项都扣掉一个 min,合起来正好是 sum − n×min。连 min 都不必真的一次次减,求出 sum 和 min 一步代入即可。
拿 nums=[1,2,3] 亲手套一遍
先求总和 sum = 1+2+3 = 6,最小值 min = 1,长度 n = 3。代入 sum − n×min = 6 − 3×1 = 3,答案就是 3。拆开看更踏实:翻译成减 1 之后,1 已经是最小、减 0 次,2 要减到 1 是 1 次,3 要减到 1 是 2 次,0+1+2 = 3,和公式合得上,也正是题面给的 3。
再验一遍 nums=[1,1,1]:sum = 3、min = 1、n = 3,3 − 3×1 = 0。三个数本来就相等,一次都不用动,公式自动给 0。
别真去循环模拟,min 和溢出这两处最容易栽
复杂度上,求 sum 扫一遍、求 min 扫一遍,都是线性,时间 O(n);只用「和」与「最小值」两个变量,空间 O(1)。
有几处容易栽跟头。基准一定得选最小值 min:要是贪图「让大家一起升到最大值 max」,前面那层「加等价于减」的转化就不成立,步数只会更多。再就是溢出——n 到 10^5、元素到 10^9,sum 和 n×min 这类中间量乘出来早破了 32 位整数的上限,C++、Java 得用 long(Python 整数天生没有上限),只在最后结果转回 int。边界也顺手:全相等时 sum − n×min 自然是 0,单个元素同理为 0,两个数套公式即得。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
记住这条转化:加 n-1 个 ≡ 减 1 个。基准是「最小值」,答案 = 总和减去 n 乘最小值。下面逐帧套它。
先把数组摆出来:4、1、3、2、5、2,一共 6 个数,和是 17。直接想「往上加」会乱,换个角度看。
核心转化:给 n-1 个各加 1,等于把剩下那个相对减 1。只看相对差,就把「加」翻译成了「减」。那大家都减到谁?减到最小的那个最省。
先扫一遍找最小值。第 0 个是 4,暂定它是最小,绿色标住。
第 1 个是 1,比当前最小 4 还小,最小值更新成 1,绿色挪到这里。
第 2 个是 3,不小于当前最小 1,最小值不变,绿色仍在下标 1。
第 3 个是 2,不小于当前最小 1,最小值不变,绿色仍在下标 1。
第 4 个是 5,不小于当前最小 1,最小值不变,绿色仍在下标 1。
第 5 个是 2,不小于当前最小 1,最小值不变,绿色仍在下标 1。
扫完了,最小值是绿色这个 1。接下来每个数都要减到 1,灰色这些就是要往下降的。一个一个算它们各要减几次。
轮到下标 0 的 4。它要降到 1,得减 4 − 1 = 3 次。
把这 3 次加进总数,累计操作变成 3。下标 0 结算完,变绿。
轮到下标 1 的 1。它要降到 1,得减 1 − 1 = 0 次,它本来就是最小,一次都不用减。
把这 0 次加进总数,累计操作变成 3。下标 1 结算完,变绿。
轮到下标 2 的 3。它要降到 1,得减 3 − 1 = 2 次。
把这 2 次加进总数,累计操作变成 5。下标 2 结算完,变绿。
轮到下标 3 的 2。它要降到 1,得减 2 − 1 = 1 次。
把这 1 次加进总数,累计操作变成 6。下标 3 结算完,变绿。
轮到下标 4 的 5。它要降到 1,得减 5 − 1 = 4 次。
把这 4 次加进总数,累计操作变成 10。下标 4 结算完,变绿。
轮到下标 5 的 2。它要降到 1,得减 2 − 1 = 1 次。
把这 1 次加进总数,累计操作变成 11。下标 5 结算完,变绿。
每个元素要减的次数加起来,正好等于 sum 减去 n 乘 min:17 − 6×1 = 11。所以最少操作 11 次,全员降到 1。
边界先想清:已相等为 0、单元素为 0、两个数套公式即得。
两个高频追问:基准为何是 min、与 LC462 的区别。
参考代码
from __future__ import annotationsfrom 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)复杂度
- 时间:O(n),求 sum 和 min 各扫一遍,常数遍线性
- 空间:O(1),只用 sum、min 两个变量
易错点
面试追问把动画讲成自己的话
追问为什么基准非得是最小值,不能是别的值?
追问这题和 LC462「最少移动次数使数组元素相等 II」有什么区别?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
扫雷游戏
LeetCode 529 · 中等 · 沿着 数组套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题