Problem overview
leaderboard receives a vector of players. Each player is a pair<string, int>: a name and a score, like {"Ava", 30}. It returns only the names, from the highest score to the lowest. Players with the same score go in alphabetical order of their names. No two players share a name.
For [("Ava", 30), ("Ben", 25), ("Cy", 40)] the result is ["Cy", "Ava", "Ben"]. For [("Mia", 50), ("Leo", 70), ("Ava", 50)] it is ["Leo", "Ava", "Mia"]: Leo leads with 70, and Ava and Mia tie at 50, so Ava comes first. For [("Zoe", 0)] it is ["Zoe"].
Approach: sort the pairs, then pull out the names
The job has two parts. First, put the players in the right order. Then build a vector with just their names, in that order.
For the ordering, give sort a lambda that says when player a goes before player b. In a pair, second is the score and first is the name. Rule one: if the scores differ, the higher score goes first, a.second > b.second. Rule two, for equal scores: the name that comes first from A to Z goes first, a.first < b.first.
Step by step
- Sort
playerswith a lambda of twoconst pair<string, int>¶meters. - Rule one: if
a.second != b.second, returna.second > b.second. The sign is "greater than" because high scores come first. - Rule two: return
a.first < b.firstfor players with equal scores. - Create an empty
vector<string> namesto collect the result. - Loop over the sorted players, unpacking each pair into
auto [name, score]. - Add each
namewithpush_back. The players are already in order, so the names come out in order. - Return
namesafter the loop.
What the sort does to the second example
The input is Mia 50, Leo 70, Ava 50. After the sort the vector holds these players:
Leo 70
Ava 50
Mia 50
Leo has the highest score, so rule one puts him first. Ava and Mia have the same score, so rule one says nothing about them (the scores don't differ) and the comparison falls through to rule two, which puts Ava before Mia. The loop then reads the names from top to bottom and builds ["Leo", "Ava", "Mia"].
The reason for the if is that the lambda must give one clear answer for every pair of players. Without it, a single line such as return a.second > b.second; would be false in both directions for two equal scores, and the sort would be free to leave them in either order. The second line only runs when the first has nothing to say.
The solution in C++
class Solution {
public:
vector<string> leaderboard(vector<pair<string, int>>& players) {
sort(players.begin(), players.end(), [](const pair<string, int>& a, const pair<string, int>& b) {
if (a.second != b.second) {
return a.second > b.second;
}
return a.first < b.first;
});
vector<string> names;
for (auto [name, score] : players) {
names.push_back(name);
}
return names;
}
};
Details worth knowing:
auto [name, score]unpacks each pair into two variables with the pair's types. The loop doesn't usescore; that is fine, because unused structured bindings are allowed.- Pairs have their own ordering. A plain
sort(players.begin(), players.end())comparesfirstand thensecond: name first, score second, both ascending. That is not the order wanted here, which is why the lambda is needed. - The function sorts the caller's vector, because
playersis passed by reference.
Another way to write the rules
The two rules can be folded into one comparison of pairs, by negating the score so that a bigger score sorts first:
sort(players.begin(), players.end(), [](const auto& a, const auto& b) {
return make_pair(-a.second, a.first) < make_pair(-b.second, b.first);
});
On the second example this gives Leo Ava Mia, like the version above. It is shorter, but the lesson's version with two plain rules is easier to read and to change.
Edge cases and common mistakes
- Wrong direction on the score.
a.second < b.secondputs the lowest score first. - Forgetting the name rule. With ties, the order of Ava and Mia would be left to chance.
- Flipping the name rule.
a.first > b.firstwould put Mia before Ava. - Returning the pairs or the scores. The result type is
vector<string>: names only. - One player.
[("Zoe", 0)]sorts trivially and returns["Zoe"].
Why sort first, then collect
Sorting the pairs and only then dropping the scores keeps each name attached to its score while the order is decided. If you pulled the names out first, the scores would be gone and nothing would tell you which name goes where. The pair keeps them together until the order is final, and the loop at the end is a plain copy of the names in that order.
The loop makes a copy of every pair, which is fine for at most 100 short players. For a large vector, for (const auto& [name, score] : players) reads the pairs in place without copying them. The two forms give the same names in the same order.
Because no two players share a name, rule two always settles a tie between two different players. Two entries can never be fully equal, so the final order is the same on every run, which is what lets the lesson's tests expect one exact list of names.
Testing
[("Ava", 30), ("Ben", 25), ("Cy", 40)]givesCy Ava Ben.[("Mia", 50), ("Leo", 70), ("Ava", 50)]givesLeo Ava Mia.[("Zoe", 0)]givesZoe.
