The Design A Leaderboard problem on LeetCode asks you to implement a simple leaderboard system that supports the following operations:
addScore(playerId, score)
: Add score
points to the player's total score. If the player does not exist, create a new entry for them.top(K)
: Return the sum of the top K
highest scores among all players.reset(playerId)
: Reset the score of the player with playerId
back to 0
.playerId
is unique.top(K)
call.
At first, you might think of storing each player's score in a simple dictionary or map. This makes addScore
and reset
easy, but top(K)
would require sorting all scores every time it's called, which is inefficient for large numbers of players or frequent queries.
To optimize, we want a way to quickly get the top K
scores without sorting the entire list every time. This suggests using a data structure that maintains order, such as a heap or a balanced binary search tree. However, since we only need the top K
scores (not the full ranking), we can use a min-heap of size K
or simply sort the values when top(K)
is called, as long as the number of players is not too large.
The main challenge is balancing efficient updates (addScore
, reset
) with efficient queries (top(K)
).
Let's break down the design step by step:
playerId
and their current score
. This gives O(1) add, update, and reset operations.playerId
exists, add score
to the current total. Otherwise, create a new entry with the score.K
scores. There are two common ways:
K
elements (simple and works well for small to moderate N).K
to keep track of the largest K
scores (more efficient for very large N).
Let's walk through a sample sequence of operations:
addScore(1, 73)
addScore(2, 56)
addScore(3, 39)
addScore(4, 51)
addScore(5, 4)
top(1) // returns 73
reset(1)
reset(2)
addScore(2, 51)
top(3) // returns 141 (51 + 51 + 39)
Step-by-step:
top(1)
: Highest score is 73 (player 1)reset(1)
: player 1 score becomes 0reset(2)
: player 2 score becomes 0addScore(2, 51)
: player 2 score becomes 51top(3)
: Top 3 scores are 51 (player 2), 51 (player 4), 39 (player 3) → sum is 141Brute-force Approach:
addScore
and reset
: O(1) each (just a dictionary update)
top(K)
: O(N log N) because we sort all scores every time, where N is the number of players
addScore
and reset
: O(1)
top(K)
: O(N log K) using a min-heap of size K
N
is very large or top(K)
is called very frequently, the simple sorting method is usually sufficient.
The Leaderboard problem is a classic example of balancing fast updates with fast queries. By using a hash map for constant-time updates and either sorting or a min-heap for efficient top-K queries, we can support all operations efficiently. The main insight is recognizing that while addScore
and reset
are straightforward, top(K)
requires careful consideration to avoid unnecessary sorting or scanning of all data. This design demonstrates the power of combining simple data structures for flexible and performant solutions.
class Leaderboard:
def __init__(self):
self.scores = {}
def addScore(self, playerId: int, score: int) -> None:
if playerId in self.scores:
self.scores[playerId] += score
else:
self.scores[playerId] = score
def top(self, K: int) -> int:
return sum(sorted(self.scores.values(), reverse=True)[:K])
def reset(self, playerId: int) -> None:
self.scores[playerId] = 0
class Leaderboard {
public:
unordered_map<int, int> scores;
Leaderboard() {}
void addScore(int playerId, int score) {
scores[playerId] += score;
}
int top(int K) {
vector<int> vals;
for (auto &p : scores)
vals.push_back(p.second);
sort(vals.rbegin(), vals.rend());
int sum = 0;
for (int i = 0; i < K && i < vals.size(); ++i)
sum += vals[i];
return sum;
}
void reset(int playerId) {
scores[playerId] = 0;
}
};
import java.util.*;
class Leaderboard {
private Map<Integer, Integer> scores;
public Leaderboard() {
scores = new HashMap<>();
}
public void addScore(int playerId, int score) {
scores.put(playerId, scores.getOrDefault(playerId, 0) + score);
}
public int top(int K) {
List<Integer> vals = new ArrayList<>(scores.values());
vals.sort(Collections.reverseOrder());
int sum = 0;
for (int i = 0; i < K && i < vals.size(); i++)
sum += vals.get(i);
return sum;
}
public void reset(int playerId) {
scores.put(playerId, 0);
}
}
class Leaderboard {
constructor() {
this.scores = new Map();
}
addScore(playerId, score) {
this.scores.set(playerId, (this.scores.get(playerId) || 0) + score);
}
top(K) {
let vals = Array.from(this.scores.values());
vals.sort((a, b) => b - a);
return vals.slice(0, K).reduce((a, b) => a + b, 0);
}
reset(playerId) {
this.scores.set(playerId, 0);
}
}