LeetCode 3322 - Premier League Table Ranking III
The problem requires calculating a Premier League-style ranking for teams in each season based on their match results.
Difficulty: 🟡 Medium
Topics: Database
Solution
Problem Understanding
The problem requires calculating a Premier League-style ranking for teams in each season based on their match results. The input is a table SeasonStats that contains the statistics for each team per season, including the number of matches played, wins, draws, losses, goals scored, and goals conceded. Each team is uniquely identified by a combination of season_id and team_id.
We need to compute three derived values for each team: points, goal difference, and position. Points are calculated using the standard football scoring system, where a win gives 3 points, a draw gives 1 point, and a loss gives 0 points. Goal difference is simply goals_for - goals_against. The position of each team within a season is determined first by total points (descending), then by goal difference (descending), and finally alphabetically by team name in ascending order in case of ties.
The expected output is a table with season_id, team_id, team_name, points, goal_difference, and position, ordered by season_id ascending, then by position ascending, and finally by team_name ascending.
Important edge cases include seasons with teams tied on points and goal difference, teams with zero wins or draws, and handling multiple seasons correctly.
Approaches
A brute-force approach would first compute points and goal difference for all teams, then iterate over each season and manually sort the teams by the ranking criteria before assigning positions. While correct, it is cumbersome and inefficient for large datasets, as it requires multiple passes over the data and explicit sorting per season.
The optimal approach leverages SQL window functions. By computing points and goal_difference in a subquery and then using RANK() or DENSE_RANK() partitioned by season_id with an appropriate ORDER BY clause, we can assign the position in a single query efficiently. This approach uses the database engine's internal sorting and ranking mechanisms, which are optimized for such operations.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n log n) per season | O(n) | Compute points and goal difference, sort manually for each season |
| Optimal | O(n log n) | O(n) | Use SQL window functions RANK() or DENSE_RANK() to assign positions efficiently |
Algorithm Walkthrough
- Compute
pointsfor each team using the formulawins * 3 + draws. - Compute
goal_differencefor each team asgoals_for - goals_against. - Use a subquery to calculate these two derived fields along with the original columns.
- Apply the
RANK()window function partitioned byseason_idand ordered bypointsdescending,goal_differencedescending, andteam_nameascending to determine thepositionof each team within their season. - Select the final columns:
season_id,team_id,team_name,points,goal_difference, andposition. - Order the final result by
season_idascending,positionascending, andteam_nameascending for deterministic output.
This works because the window function correctly partitions the data by season and computes the rank according to the specified order, ensuring that ties are handled correctly and positions are assigned in accordance with the problem description.
Python Solution
# LeetCode problem requires a SQL solution, but for completeness, here is a Python equivalent
from typing import List, Dict
def premier_league_ranking(stats: List[Dict]) -> List[Dict]:
for team in stats:
team['points'] = team['wins'] * 3 + team['draws']
team['goal_difference'] = team['goals_for'] - team['goals_against']
result = []
seasons = sorted(set(team['season_id'] for team in stats))
for season in seasons:
season_teams = [team for team in stats if team['season_id'] == season]
season_teams.sort(key=lambda x: (-x['points'], -x['goal_difference'], x['team_name']))
for idx, team in enumerate(season_teams, 1):
result.append({
'season_id': team['season_id'],
'team_id': team['team_id'],
'team_name': team['team_name'],
'points': team['points'],
'goal_difference': team['goal_difference'],
'position': idx
})
return result
In this Python implementation, we first augment the original data with points and goal_difference. We then sort the teams for each season by the ranking criteria and enumerate to assign positions. This mirrors the SQL window function approach in a procedural context.
Go Solution
package main
import (
"sort"
)
type Team struct {
SeasonID int
TeamID int
TeamName string
MatchesPlayed int
Wins int
Draws int
Losses int
GoalsFor int
GoalsAgainst int
Points int
GoalDifference int
Position int
}
func PremierLeagueRanking(teams []Team) []Team {
seasonMap := make(map[int][]*Team)
for i := range teams {
teams[i].Points = teams[i].Wins*3 + teams[i].Draws
teams[i].GoalDifference = teams[i].GoalsFor - teams[i].GoalsAgainst
seasonMap[teams[i].SeasonID] = append(seasonMap[teams[i].SeasonID], &teams[i])
}
seasons := []int{}
for season := range seasonMap {
seasons = append(seasons, season)
}
sort.Ints(seasons)
var result []Team
for _, season := range seasons {
seasonTeams := seasonMap[season]
sort.Slice(seasonTeams, func(i, j int) bool {
if seasonTeams[i].Points != seasonTeams[j].Points {
return seasonTeams[i].Points > seasonTeams[j].Points
}
if seasonTeams[i].GoalDifference != seasonTeams[j].GoalDifference {
return seasonTeams[i].GoalDifference > seasonTeams[j].GoalDifference
}
return seasonTeams[i].TeamName < seasonTeams[j].TeamName
})
for pos, team := range seasonTeams {
team.Position = pos + 1
result = append(result, *team)
}
}
return result
}
The Go implementation follows the same logic as the Python version but handles sorting with sort.Slice and uses pointers to efficiently update positions within slices.
Worked Examples
Using the provided input for the 2021 season:
| team_name | wins | draws | losses | goals_for | goals_against | points | goal_difference |
|---|---|---|---|---|---|---|---|
| Manchester City | 29 | 6 | 3 | 99 | 26 | 93 | 73 |
| Liverpool | 28 | 8 | 2 | 94 | 26 | 92 | 68 |
| Chelsea | 21 | 11 | 6 | 76 | 33 | 74 | 43 |
| Tottenham | 22 | 5 | 11 | 69 | 40 | 71 | 29 |
| Arsenal | 22 | 3 | 13 | 61 | 48 | 69 | 13 |
After sorting by points, goal difference, and team name:
| position | team_name | points | goal_difference |
|---|---|---|---|
| 1 | Manchester City | 93 | 73 |
| 2 | Liverpool | 92 | 68 |
| 3 | Chelsea | 74 | 43 |
| 4 | Tottenham | 71 | 29 |
| 5 | Arsenal | 69 | 13 |
This matches the expected output.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log n) | Sorting all teams within each season dominates the complexity |
| Space | O(n) | Storing augmented team data and intermediate season lists |
The complexity is primarily determined by sorting the teams for each season. Using SQL window functions delegates this sorting to the database engine.
Test Cases
# Provided example
assert premier_league_ranking([
{'season_id': 2021, 'team_id': 1, 'team_name': 'Manchester City', 'matches_played': 38, 'wins': 29, 'draws': 6, 'losses': 3, 'goals_for': 99, 'goals_against': 26},
{'season_id': 2021, 'team_id': 2, 'team_name': 'Liverpool', 'matches_played': 38, 'wins': 28, 'draws': 8, 'losses': 2, 'goals_for': 94, 'goals_against': 26},
{'season_id': 2021, 'team_id': 3, 'team_name': 'Chelsea', 'matches_played': 38, 'wins': 21, 'draws': 11, 'losses': 6, 'goals_for': 76