运动员和训练师的最大匹配数 图解题解
这道题到底在问什么
- 输入
- players=[4,7,9], trainers=[8,2,5,8]
- 输出
- 2
- 输入
- players=[1,1,1], trainers=[10]
- 输出
- 1
最优解:为什么这么做
一句话答案:LeetCode 2410 运动员和训练师的最大匹配数:两个能力数组各自排序后双指针贪心,让最弱运动员配够得上他的最弱训练师,配上就双双前进。时间 O(m log m + n log n)、空间 O(1)。
运动员和训练师,最多能凑成几对搭档
给一排运动员的能力 players 和一排训练师的能力 trainers。运动员能力小于等于训练师能力就能配成一对;一名运动员至多配一位训练师,一位训练师至多带一名运动员。求最多配成几对。题面 players=[4,7,9]、trainers=[8,2,5,8] 答案是 2:能力 4、7 各配一个 8,最强的 9 谁都够不上,落空。
给每名运动员挨个翻训练师,卡在哪
对每名运动员从头翻还没占用的训练师、挑个够得上的配上,m 名运动员每人最坏翻 n 位,时间 O(m·n),两边上千位就跑不动。更麻烦的是「配哪一位」没准谱:一名运动员常有好几位训练师够格,随手挑,可能占掉本该留给别人的搭档,总对数反而少。
为什么让最弱的运动员先挑,反而配得最多
把两个数组都从小到大排好,一条朴素又正确的规则就浮出来:让当前最弱的运动员,配够得上他的、能力最弱的那位训练师。这套「每步都挑当下最省」的贪心在这儿成立:强训练师留着还能压更强的运动员,弱运动员门槛最低,拿刚够格的打发他最不浪费。
为什么最优?把最优解一步步换成贪心、看对数会不会掉,就说清了:假设某最优解里最弱运动员没配那位刚够格的最弱训练师 t,t 要么闲着、要么被更强的运动员 q 占着。t 闲着就把弱运动员挪到 t 上多配一对;t 被 q 占,就让两人对换,弱运动员拿走本就够格的 t,q 顶到弱运动员原先那位更强训练师、照样压得住。对数没少却成了贪心,可见贪心不比最优差。
两个指针怎么走,才不用回头重扫
排好序后两个指针一齐往右挪。i 指当前要配的运动员(从最弱起),j 指当前考察的训练师。只要 trainers[j] < players[i],这训练师太弱、对更强的运动员更没用,j 往右跳过;一旦 trainers[j] ≥ players[i],配成一对,i、j 同时前进。若 j 走到队尾还没配上,剩下的运动员更强、都没搭档,对数就此定下。i、j 各只走一趟不回头,扫描是线性的。
题面 [4,7,9] 配 [8,2,5,8],指针怎么挪
排完序 players=[4,7,9]、trainers=[2,5,8,8],j 从 0 起。运动员 4:j 指训练师 2,2<4 托不起,j 挪到 1;训练师 5≥4 够格,配成第 1 对,i、j 各进一格,j 到 2。运动员 7:j 指训练师 8,8≥7,配成第 2 对,j 进到 3。运动员 9:j 指训练师 8,8<9 太弱,j 挪到 4 越过队尾,训练师用光,这名运动员配不上,收工。配成 2 对就是最大匹配数。
排序省不掉,配成一对后 j 一步都不能赖着
开销压在两次排序上,时间 O(m log m + n log n);双指针只线性扫一遍,被排序盖过。空间除排序内部的栈外只几个指针,O(1)。最容易栽的是省掉排序直接上双指针:贪心全靠「从小到大」撑着,两队不排好,「最弱配够得上的最弱」就无从谈起,答案随手就偏。配成一对后 j 赖着不动是另一处坑:一位训练师只带一人,配上就用掉了,j 不前进会把同一位重复算进好几对。还有判断够格要用小于等于:相等也能配,写成严格小于会漏掉打平的两人。
▶ 动画逐步走查(共 21 步)——想跟着动画一帧帧对照就展开
- 3记牢这一句:排完序后,让最弱的运动员去配够得上他的最弱训练师。这样强的训练师就被省下来留给强运动员,谁都不浪费,配出来的对数就是最多的。下面从最弱的运动员开始,一位一位配。
- 4先看没排序的原始数据。上面横排是训练师的能力 7、2、9、3、11、7、4,侧栏是运动员的能力 8、5、13、6、10。这样乱着放没法贪心,贪心的前提是两边都得从小到大排好队,这样才能让最弱的先配最弱的。
- 5两边各自排好序。训练师从小到大是 2、3、4、7、7、9、11,运动员从小到大是 5、6、8、10、13。现在准备工作做完了,让指针 j 从最左边的训练师开始,依次给每位运动员找搭档。
- 6规则再明确一下:侧栏最上面高亮的是当前轮到的运动员,永远从最弱的那位开始;主舞台上高亮的是指针 j 当前指着的训练师。给这位运动员找搭档时,j 只会往右走,不回头。够格就染绿配对,太弱就染灰跳过。开始。
- 7轮到能力 5 的运动员。指针 j 没有回头,接着从上一位停下的地方往右看,现在指着能力 2 的训练师。看看这位训练师托不托得起 5。
- 8训练师能力 2 比运动员 5 还小,带不动他,配不了。而且后面的运动员只会更强,这位训练师对谁都没用了,直接染灰划掉,指针 j 往右挪一格。
- 9指针 j 落到能力 3 的训练师上,继续拿它和运动员 5 比,看这次够不够格。
- 10训练师能力 3 比运动员 5 还小,带不动他,配不了。而且后面的运动员只会更强,这位训练师对谁都没用了,直接染灰划掉,指针 j 往右挪一格。
- 11指针 j 落到能力 4 的训练师上,继续拿它和运动员 5 比,看这次够不够格。
- 12训练师能力 4 比运动员 5 还小,带不动他,配不了。而且后面的运动员只会更强,这位训练师对谁都没用了,直接染灰划掉,指针 j 往右挪一格。
- 13指针 j 落到能力 7 的训练师上,继续拿它和运动员 5 比,看这次够不够格。
- 14训练师能力 7 大于等于运动员 5,托得起!这一对成了,把这位训练师染绿。运动员 5 也算配上了。已匹配数加到 1。接着两个指针一起往后挪,服务下一位更强的运动员。
- 15轮到能力 6 的运动员。指针 j 没有回头,接着从上一位停下的地方往右看,现在指着能力 7 的训练师。看看这位训练师托不托得起 6。
- 16训练师能力 7 大于等于运动员 6,托得起!这一对成了,把这位训练师染绿。运动员 6 也算配上了。已匹配数加到 2。接着两个指针一起往后挪,服务下一位更强的运动员。
- 17轮到能力 8 的运动员。指针 j 没有回头,接着从上一位停下的地方往右看,现在指着能力 9 的训练师。看看这位训练师托不托得起 8。
- 18训练师能力 9 大于等于运动员 8,托得起!这一对成了,把这位训练师染绿。运动员 8 也算配上了。已匹配数加到 3。接着两个指针一起往后挪,服务下一位更强的运动员。
- 19轮到能力 10 的运动员。指针 j 没有回头,接着从上一位停下的地方往右看,现在指着能力 11 的训练师。看看这位训练师托不托得起 10。
- 20训练师能力 11 大于等于运动员 10,托得起!这一对成了,把这位训练师染绿。运动员 10 也算配上了。已匹配数加到 4。接着两个指针一起往后挪,服务下一位更强的运动员。
- 21轮到能力 13 的运动员,可指针 j 已经扫到训练师队伍的尽头,一个能用的训练师都不剩了。这位运动员再强也没人带,只能落空。
- 22既然连最弱的运动员之后的强手都配光了训练师,后面比他更强的运动员更不可能有搭档,直接收工。此刻已经配好的对数就是答案 4。
- 23回看全程。运动员 5、6、8、10 各自配到了一位刚好够得上的训练师,四位训练师染成绿色;被跳过的弱训练师染灰。最强的运动员 13 到最后没有训练师托得起,落空了。数一数绿色的对子,一共 4 对,这就是最大匹配数。
⚠️ 容易写错的地方
✗ 错:不排序直接双指针
✓ 对:两个数组都必须先从小到大排序
贪心成立的前提是「最弱运动员配够得上的最弱训练师」,不排序就没有从小到大的顺序,贪心的正确性直接崩掉
✗ 错:让强运动员优先挑训练师
✓ 对:永远让最弱的运动员先配
强运动员挑走本可留给他的训练师没问题,但若强者先挑走了一个刚够弱者用的训练师,弱者反而可能没得配,总对数变少
✗ 错:配对后指针 j 不前进
✓ 对:配成一对后 j 必须往后挪
一位训练师只能带一名运动员,配上后这位训练师就用掉了,j 不前进会让同一位训练师被重复使用
✗ 错:用严格小于判断能配
✓ 对:能力相等也能配,判断要用小于等于
题目说运动员能力小于等于训练师能力即可配对,相等是允许的,用严格小于会漏掉相等这类合法配对
完整代码(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 matchPlayersAndTrainers(self, players: List[int], trainers: List[int]) -> int:
players.sort()
trainers.sort()
j, n = 0, len(trainers)
for i, p in enumerate(players):
while j < n and trainers[j] < p:
j += 1
if j == n:
return i
j += 1
return len(players)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 matchPlayersAndTrainers(vector<int>& players, vector<int>& trainers) {
sort(players.begin(), players.end());
sort(trainers.begin(), trainers.end());
int m = players.size(), n = trainers.size();
for (int i = 0, j = 0; i < m; ++i, ++j) {
while (j < n && trainers[j] < players[i]) {
++j;
}
if (j == n) {
return i;
}
}
return m;
}
};Java
import java.util.*;
class Solution {
public int matchPlayersAndTrainers(int[] players, int[] trainers) {
Arrays.sort(players);
Arrays.sort(trainers);
int m = players.length, n = trainers.length;
for (int i = 0, j = 0; i < m; ++i, ++j) {
while (j < n && trainers[j] < players[i]) {
++j;
}
if (j == n) {
return i;
}
}
return m;
}
}复杂度
时间
O(m log m + n log n)
m 名运动员、n 位训练师。主要开销是两次排序,分别是 m log m 和 n log n;后面的双指针扫描里 i 和 j 各自只从头走到尾、不回头,是 O(m + n),被排序开销盖过
空间
O(1) 辅助 · 排序内部因语言而异
不额外开数组,只用几个指针变量,辅助空间 O(1)。排序内部开销因语言而异:C 加加和 Java 通常是 O(log m + log n) 级的递归栈,Python 的排序最坏可到 O(m + n);若把排序内部也算上,空间随语言在这两档之间
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 运动员和训练师的最大匹配数 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么非得让最弱的运动员先配,让强者先挑不行吗?+
强者先挑,麻烦在他可能顺手占走一位刚好够弱者用的训练师。举例排好序的运动员 4、7 碰训练师只有一个 8:让 7 先挑占了 8,轮到 4 就没训练师了,只配成一对;反过来让最弱的 4 先配够得上的最弱训练师,4 配上 8,7 再看剩下的,一对照样保住。让弱者先配够格里最弱的,绝不会挤掉别人的机会,强者留到后面拿更强的训练师即可。
判断能不能配,为什么用小于等于而不是严格小于?+
题目说的是运动员能力「小于等于」训练师能力就能配对,相等也算数。用严格小于会把能力打平的两人判成配不了,白白漏掉合法搭档,比如运动员 8 碰上训练师 8,本该成一对却被跳过,最终对数偏少。参考代码里写的是 trainers[j] < players[i] 时才跳过这位训练师,反过来即大于等于就配,正好对应「小于等于能配」。
复杂度瓶颈在哪,双指针那趟为什么不回头也不会漏配?+
瓶颈是两次排序,时间 O(m log m + n log n);排完序双指针只线性扫一遍,i、j 各自从头走到尾,是 O(m + n),被排序盖过,空间 O(1)。不回头也不漏,是因为一旦某训练师撑不起当前(也是往后最弱的)运动员,它对后面只会更强的运动员更没用,跳过它绝不错过任何合法配对;配成一对就双双前进,同一位训练师也不会被重复用。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 运动员和训练师的最大匹配数 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。