移除石子的最大得分 图解题解
这道题到底在问什么
- 输入
- a=2, b=4, c=6
- 输出
- 6
- 输入
- a=4, b=4, c=6
- 输出
- 7
- 输入
- a=1, b=8, c=8
- 输出
- 8
最优解:一步一步想明白
- 3记牢一句:每回合削当前最高的两堆,让三堆拉平。也可以直接套公式 min(总和减最大堆, 总和//2)。下面逐回合演示。
- 4上面三根柱子就是三堆石子:第一堆 6 颗,第二堆 7 颗,第三堆 8 颗,一共 21 颗。右边记分板显示当前得分 0,和场上还剩多少石子。接下来每回合都从当前最高的两堆各拿一颗、加一分,看着石子一点点被削平。
- 5第 1 回合。眼下三堆是 6、7、8。把它们从小到大看是 6 ≤ 7 ≤ 8,最高的两堆分别是 8 和 7(标绿的这两根)。就从这两堆各取一颗,把它们压下去,别让最高的堆越拖越突出。
- 6从这两堆各拿走一颗:一堆从 8 减到 7,另一堆从 7 减到 6,得分加到 1。三堆现在是 6、6、7。还都能继续配对,进入下一回合。
- 7第 2 回合。眼下三堆是 6、6、7。把它们从小到大看是 6 ≤ 6 ≤ 7,最高的两堆分别是 7 和 6(标绿的这两根)。就从这两堆各取一颗,把它们压下去,别让最高的堆越拖越突出。
- 8从这两堆各拿走一颗:一堆从 7 减到 6,另一堆从 6 减到 5,得分加到 2。三堆现在是 5、6、6。还都能继续配对,进入下一回合。
- 9第 3 回合。眼下三堆是 5、6、6。把它们从小到大看是 5 ≤ 6 ≤ 6,最高的两堆分别是 6 和 6(标绿的这两根)。就从这两堆各取一颗,把它们压下去,别让最高的堆越拖越突出。
- 10从这两堆各拿走一颗:一堆从 6 减到 5,另一堆从 6 减到 5,得分加到 3。三堆现在是 5、5、5。还都能继续配对,进入下一回合。
- 11第 4 回合。眼下三堆是 5、5、5。把它们从小到大看是 5 ≤ 5 ≤ 5,最高的两堆分别是 5 和 5(标绿的这两根)。就从这两堆各取一颗,把它们压下去,别让最高的堆越拖越突出。
- 12从这两堆各拿走一颗:一堆从 5 减到 4,另一堆从 5 减到 4,得分加到 4。三堆现在是 4、4、5。还都能继续配对,进入下一回合。
- 13第 5 回合。眼下三堆是 4、4、5。把它们从小到大看是 4 ≤ 4 ≤ 5,最高的两堆分别是 5 和 4(标绿的这两根)。就从这两堆各取一颗,把它们压下去,别让最高的堆越拖越突出。
- 14从这两堆各拿走一颗:一堆从 5 减到 4,另一堆从 4 减到 3,得分加到 5。三堆现在是 3、4、4。还都能继续配对,进入下一回合。
- 15第 6 回合。眼下三堆是 3、4、4。把它们从小到大看是 3 ≤ 4 ≤ 4,最高的两堆分别是 4 和 4(标绿的这两根)。就从这两堆各取一颗,把它们压下去,别让最高的堆越拖越突出。
- 16从这两堆各拿走一颗:一堆从 4 减到 3,另一堆从 4 减到 3,得分加到 6。三堆现在是 3、3、3。还都能继续配对,进入下一回合。
- 17第 7 回合。眼下三堆是 3、3、3。把它们从小到大看是 3 ≤ 3 ≤ 3,最高的两堆分别是 3 和 3(标绿的这两根)。就从这两堆各取一颗,把它们压下去,别让最高的堆越拖越突出。
- 18从这两堆各拿走一颗:一堆从 3 减到 2,另一堆从 3 减到 2,得分加到 7。三堆现在是 2、2、3。还都能继续配对,进入下一回合。
- 19第 8 回合。眼下三堆是 2、2、3。把它们从小到大看是 2 ≤ 2 ≤ 3,最高的两堆分别是 3 和 2(标绿的这两根)。就从这两堆各取一颗,把它们压下去,别让最高的堆越拖越突出。
- 20从这两堆各拿走一颗:一堆从 3 减到 2,另一堆从 2 减到 1,得分加到 8。三堆现在是 1、2、2。还都能继续配对,进入下一回合。
- 21第 9 回合。眼下三堆是 1、2、2。把它们从小到大看是 1 ≤ 2 ≤ 2,最高的两堆分别是 2 和 2(标绿的这两根)。就从这两堆各取一颗,把它们压下去,别让最高的堆越拖越突出。
- 22从这两堆各拿走一颗:一堆从 2 减到 1,另一堆从 2 减到 1,得分加到 9。三堆现在是 1、1、1。还都能继续配对,进入下一回合。
- 23第 10 回合。眼下三堆是 1、1、1。把它们从小到大看是 1 ≤ 1 ≤ 1,最高的两堆分别是 1 和 1(标绿的这两根)。就从这两堆各取一颗,把它们压下去,别让最高的堆越拖越突出。
- 24从这两堆各拿走一颗:一堆从 1 减到 0,另一堆从 1 减到 0,得分加到 10。三堆现在是 0、0、1。有堆见底了,场上空堆变多,要留意还够不够两堆非空。
- 25现在三堆是 0、0、1,其中两堆已经空了(标灰)。规则说有两个空堆就停,剩下标红那 1 颗石子孤零零一堆,一回合得动两堆,它再也找不到搭档,只能剩着。最终得分 10。用公式验一下:总和 21 减最大堆 8 得 13,总和的一半是 10,两者取小正好是 10,对上了。
⚠️ 容易写错的地方
✗ 错:每回合只从一堆猛取,或者随手挑两堆
✓ 对:一回合必须动两个不同的非空堆各取一颗;而且要取当前最高的两堆
规则要求一次动两堆,只削一堆是犯规。挑堆也有讲究:老是留着最大堆不动,等另两堆空了它就落单,里面石子全浪费;先削最高的两堆才能把三堆拉平、多凑回合
✗ 错:直接用总和除以二当答案
✓ 对:答案是 min(总和减最大堆, 总和//2),要先比一比
当最大堆比另外两堆之和还大时,最大堆里多出来的石子永远配不到对,得分被卡在「总和减最大堆」上,不到总和的一半。像 a=1,b=2,c=10,答案是 3 而不是 6
✗ 错:以为三堆总能刚好配完、不剩石子
✓ 对:总和是奇数时,削到最后必然剩一颗孤子,得分要向下取整
一回合消掉两颗,总消耗永远是偶数。总和为奇数时最后一定余一颗单的,它配不成对,所以能拉平时得分是「总和除以二向下取整」,比如总和 21 时是 10 不是 10.5
完整代码(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 maximumScore(self, a: int, b: int, c: int) -> int:
s = sorted([a, b, c])
ans = 0
while s[1]:
ans += 1
s[1] -= 1
s[2] -= 1
s.sort()
return ansC++
#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 maximumScore(int a, int b, int c) {
vector<int> s = {a, b, c};
sort(s.begin(), s.end());
int ans = 0;
while (s[1]) {
++ans;
s[1]--;
s[2]--;
sort(s.begin(), s.end());
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int maximumScore(int a, int b, int c) {
int[] s = new int[] {a, b, c};
Arrays.sort(s);
int ans = 0;
while (s[1] > 0) {
++ans;
s[1]--;
s[2]--;
Arrays.sort(s);
}
return ans;
}
}复杂度
时间
O(a+b+c)
每回合总石子数减 2,所以回合数最多是总和的一半,也就是 O(a+b+c) 这个量级;每回合里排序的只有三个元素,是常数功夫。若改用等价公式 min(总和减最大堆, 总和//2) 直接算,则是 O(1)
空间
O(1)
按峰值算,自始至终只用一个长度为 3 的数组存三堆、外加得分和几个下标变量,占用是固定的常数,和石子多少无关,所以是 O(1)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 移除石子的最大得分 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么每回合取当前最高的两堆一定是最优的?+
可以用交换的思路想:最终答案受限于两件事,一是每回合消两颗、总回合不超过总和的一半,二是最大那堆里超出另两堆之和的部分永远没搭档。每回合削最高的两堆,既在稳稳地凑「总和一半」这个上限,又在尽量把最大堆压到不超过另两堆之和,两个上限它都在顶着够。要是某回合没削最大堆,最大堆就更容易最后落单,反而拿不满,所以贪心削最高两堆是最优。
不用一回合回合地模拟,能直接算出答案吗?+
能,一个公式就够。设三堆总和是 s、最大堆是 m。分两种情况:如果最大堆比另两堆之和还大,也就是 m 大于 s 减 m,那多出来的石子配不了对,答案就是另两堆之和 s 减 m;否则三堆能拉平,答案是 s 除以二向下取整。合起来就是 min(s 减 m, s 除以二向下取整),一步 O(1) 算完,比模拟快得多。
这题和「任务调度器」「重构字符串」这类题有什么共性?+
它们都是「数量最多的那一类会不会把其余卡住」的配对或间隔问题。判断的关键量都一样:拿数量最大的一项和其余各项之和去比。最大项没超过其余之和,就能均匀铺开、答案由总量决定;最大项超了,答案就被最大项单独卡住。认出这个套路,一大类题都能用同一把尺子量。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 移除石子的最大得分 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。