Before attempting this problem, you should be comfortable with:
The simplest approach stores player scores in a hash map. Adding a score or resetting is straightforward with hash map operations. To find the sum of the top K scores, we extract all scores, sort them in descending order, and sum the first K values. While easy to implement, sorting all scores for every top query is inefficient when there are many players.
scores to store the mapping from playerId to their score.addScore(playerId, score): if the player exists, add to their score; otherwise, initialize and add.top(K): extract all score values, sort them in descending order, and return the sum of the first K scores.reset(playerId): set the player's score to 0 in the hash map.class Leaderboard:
def __init__(self):
self.scores = defaultdict()
def addScore(self, playerId: int, score: int) -> None:
if playerId not in self.scores:
self.scores[playerId] = 0
self.scores[playerId] += score
def top(self, K: int) -> int:
values = [v for _, v in sorted(self.scores.items(), key=lambda item: item[1])]
values.sort(reverse=True)
total, i = 0, 0
while i < K:
total += values[i]
i += 1
return total
def reset(self, playerId: int) -> None:
self.scores[playerId] = 0Time complexity:
addScoreresettopSpace complexity:
Where is the total number of players in the leaderboard.
Instead of sorting all N scores, we can use a min-heap of size K to find the top K scores more efficiently. As we iterate through all scores, we maintain a heap containing the K largest scores seen so far. When the heap exceeds size K, we remove the smallest element. After processing all scores, the heap contains exactly the top K scores, and we sum them up.
scores to store the mapping from playerId to their score.addScore(playerId, score): if the player exists, add to their score; otherwise, initialize and add.top(K):K, pop the minimum.reset(playerId): set the player's score to 0 in the hash map.class Leaderboard:
def __init__(self):
self.scores = {}
def addScore(self, playerId: int, score: int) -> None:
if playerId not in self.scores:
self.scores[playerId] = 0
self.scores[playerId] += score
def top(self, K: int) -> int:
# This is a min-heap by default in Python.
heap = []
for x in self.scores.values():
heapq.heappush(heap, x)
if len(heap) > K:
heapq.heappop(heap)
res = 0
while heap:
res += heapq.heappop(heap)
return res
def reset(self, playerId: int) -> None:
self.scores[playerId] = 0Time complexity:
addScoreresettopSpace complexity:
Where is the total number of players in the leaderboard, and is the number of top-scoring players.
A TreeMap (or sorted dictionary) keeps scores in sorted order, allowing us to iterate from highest to lowest efficiently. The key insight is to track how many players share each score rather than storing individual player entries. When a score changes, we decrement the count for the old score and increment for the new one. For top(K), we iterate through scores in descending order, accumulating until we reach K players.
scores: a hash map from playerId to their score.sortedScores: a TreeMap from score to the count of players with that score, sorted in descending order.addScore(playerId, score):sortedScores (removing the entry if count becomes 0), update scores, and increment the count for the new score.top(K):sortedScores in descending order.K players have been counted.reset(playerId): decrement the count for the player's score in sortedScores (removing if 0), and remove the player from scores.from sortedcontainers import SortedDict
class Leaderboard:
def __init__(self):
self.scores = {}
self.sortedScores = SortedDict()
def addScore(self, playerId: int, score: int) -> None:
# The scores dictionary simply contains the mapping from the
# playerId to their score. The sortedScores contain a BST with
# key as the score and value as the number of players that have
# that score.
if playerId not in self.scores:
self.scores[playerId] = score
self.sortedScores[-score] = self.sortedScores.get(-score, 0) + 1
else:
preScore = self.scores[playerId]
val = self.sortedScores.get(-preScore)
if val == 1:
del self.sortedScores[-preScore]
else:
self.sortedScores[-preScore] = val - 1
newScore = preScore + score
self.scores[playerId] = newScore
self.sortedScores[-newScore] = self.sortedScores.get(-newScore, 0) + 1
def top(self, K: int) -> int:
count, total = 0, 0
for key, value in self.sortedScores.items():
times = self.sortedScores.get(key)
for _ in range(times):
total += -key
count += 1
# Found top-K scores, break.
if count == K:
break
# Found top-K scores, break.
if count == K:
break
return total
def reset(self, playerId: int) -> None:
preScore = self.scores[playerId]
if self.sortedScores[-preScore] == 1:
del self.sortedScores[-preScore]
else:
self.sortedScores[-preScore] -= 1
del self.scores[playerId]Time complexity:
for addScore
for reset. Note that this complexity is in the case when every player always maintains a unique score.
for top. Note that if the data structure doesn't provide a natural iterator, then we can simply get a list of all the key-value pairs and they will naturally be sorted due to the nature of this data structure. In that case, the complexity would be since we would be forming a new list.
Space complexity: used by the scores dictionary. Also, if you obtain all the key-value pairs in a new list in the top function, then an additional would be used.
Where is the total number of players in the leaderboard, and is the number of top-scoring players.
The reset function should remove the player's score entirely or set it to zero, depending on the problem requirements. Using the wrong approach affects how the top(K) calculation handles that player.
# Approach 1: Set score to 0 (player still exists with 0 score)
def reset(self, playerId):
self.scores[playerId] = 0 # Will be included in calculations
# Approach 2: Remove player entirely (cleaner for TreeMap approach)
def reset(self, playerId):
del self.scores[playerId] # Player no longer countedWhen using a TreeMap to track scores, multiple players can have the same score. You must store a count of players per score, not just a single player. Otherwise, adding a second player with the same score will overwrite the first.
# Wrong: Only stores one player per score
self.sortedScores[score] = playerId # Overwrites previous player!
# Correct: Store count of players with each score
self.sortedScores[score] = self.sortedScores.get(score, 0) + 1When a player's score changes via addScore, you must update both the player-to-score mapping and the sorted scores structure. Forgetting to remove the old score from the sorted structure leads to stale data.
# Wrong: Only updates player score
def addScore(self, playerId, score):
self.scores[playerId] = self.scores.get(playerId, 0) + score
# Sorted structure still has old score!
# Correct: Update both structures
def addScore(self, playerId, score):
if playerId in self.scores:
oldScore = self.scores[playerId]
# Remove old score from sorted structure
self.sortedScores[oldScore] -= 1
if self.sortedScores[oldScore] == 0:
del self.sortedScores[oldScore]
newScore = self.scores.get(playerId, 0) + score
self.scores[playerId] = newScore
# Add new score to sorted structure
self.sortedScores[newScore] = self.sortedScores.get(newScore, 0) + 1