LeetCode 1412 - Find the Quiet Students in All Exams
This problem asks us to identify students who are "quiet" across all exams they participated in. A student is considered
Difficulty: 🔴 Hard
Topics: Database
Solution
Problem Understanding
This problem asks us to identify students who are "quiet" across all exams they participated in. A student is considered quiet if, in every exam they took, they never achieved either the highest score or the lowest score.
We are given two tables:
- The
Studenttable contains student IDs and names. - The
Examtable records exam participation and scores.
Each row in the Exam table represents one student's score in one specific exam. Multiple students can belong to the same exam.
The important detail is that we are not looking for students who were quiet in at least one exam. Instead, we want students who were quiet in every exam they took.
For each exam:
- Any student with the maximum score is disqualified.
- Any student with the minimum score is disqualified.
- If a student ever appears in either category, they cannot be part of the final result.
The final answer should include:
- Students who took at least one exam
- Students who were never the highest scorer
- Students who were never the lowest scorer
The output must be ordered by student_id.
Consider the sample carefully:
- Student 1 repeatedly receives the minimum score.
- Student 3 and Student 4 receive maximum scores.
- Student 2 participates and never becomes highest or lowest.
- Student 5 never takes an exam.
Therefore:
- Student 2 is included.
- Student 5 is excluded because the problem explicitly says students with no exams should not appear.
Several edge cases are important here.
If an exam has only one participant, that student is simultaneously both highest and lowest, meaning they are immediately disqualified.
If multiple students tie for the highest or lowest score, every tied student is considered non-quiet and must be excluded.
A student may participate in many exams and only need to fail once to become invalid.
The problem is fundamentally about filtering students based on aggregate exam behavior.
Approaches
Brute Force Approach
A straightforward solution is to process each student independently.
For every student:
- Find all exams they participated in.
- For each of those exams:
- Compute the minimum score
- Compute the maximum score
- Compare the student's score against those values
- If the student's score ever equals either extreme, mark them as non-quiet.
This works because it directly follows the problem definition. However, it is inefficient because minimum and maximum scores for the same exam are recomputed repeatedly for different students.
Suppose an exam contains 1000 students. If we recompute min/max for every participant independently, we repeatedly scan the same data many times.
This creates unnecessary repeated work and leads to poor scalability.
Optimal Approach
The key observation is that exam-level information should be computed once.
For every exam:
- Determine the minimum score
- Determine the maximum score
- Mark all students who match either boundary score as disqualified
After processing all exams:
- Collect students who participated in at least one exam
- Exclude all disqualified students
- Return the remaining students
This approach is efficient because:
- Each exam is analyzed once
- Each row is processed a constant number of times
- We use set-based filtering instead of repeated rescanning
In SQL, this maps naturally to:
- Window functions or aggregation
- A set of disqualified students
- Final filtering using
NOT INor joins
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(S × E) | O(1) | Recomputes min/max repeatedly for each student |
| Optimal | O(E) | O(E) | Computes exam boundaries once and filters efficiently |
Here:
Sis the number of studentsEis the number of exam rows
Algorithm Walkthrough
Using SQL Aggregation and Filtering
- First, compute the minimum and maximum score for every exam.
We group rows by exam_id and calculate:
MIN(score)MAX(score)
This gives us the score boundaries for each exam. 2. Next, identify students who are not quiet.
We join the original Exam table with the exam statistics and check:
score = min_scorescore = max_score
Any matching student becomes disqualified. 3. Store all disqualified student IDs.
A student only needs one bad exam to become invalid, so we use DISTINCT to avoid duplicates.
4. Identify students who actually participated in exams.
The problem explicitly excludes students who never took an exam.
5. Join with the Student table.
This allows us to retrieve student names. 6. Exclude all disqualified students.
We keep only students whose IDs are not present in the disqualified set.
7. Sort the final output by student_id.
Why it works
The algorithm works because every exam independently defines a set of invalid students:
- students with minimum scores
- students with maximum scores
A quiet student must avoid both categories in every exam they take.
By collecting the union of all invalid students and excluding them from participating students, we exactly capture the required definition.
Python Solution
Although this is a Database problem, the following Python implementation demonstrates the same logic algorithmically.
from collections import defaultdict
from typing import List
class Solution:
def quietStudents(
self,
students: List[List],
exams: List[List[int]]
) -> List[List]:
exam_scores = defaultdict(list)
for exam_id, student_id, score in exams:
exam_scores[exam_id].append((student_id, score))
disqualified = set()
participated = set()
for exam_id, entries in exam_scores.items():
scores = [score for _, score in entries]
min_score = min(scores)
max_score = max(scores)
for student_id, score in entries:
participated.add(student_id)
if score == min_score or score == max_score:
disqualified.add(student_id)
result = []
for student_id, student_name in students:
if (
student_id in participated
and student_id not in disqualified
):
result.append([student_id, student_name])
result.sort(key=lambda x: x[0])
return result
The implementation begins by grouping exam records by exam_id. This allows us to process each exam independently.
For every exam, we compute:
- the minimum score
- the maximum score
We then iterate through all participants in that exam. Any student whose score matches either boundary becomes disqualified.
At the same time, we track which students participated in at least one exam.
Finally, we iterate through the student list and keep only students who:
- participated in exams
- never appeared in the disqualified set
The result is sorted before returning.
Go Solution
package main
import (
"sort"
)
type Student struct {
ID int
Name string
}
func quietStudents(
students []Student,
exams [][]int,
) [][]interface{} {
examScores := make(map[int][][]int)
for _, exam := range exams {
examID := exam[0]
studentID := exam[1]
score := exam[2]
examScores[examID] = append(
examScores[examID],
[]int{studentID, score},
)
}
disqualified := make(map[int]bool)
participated := make(map[int]bool)
for _, entries := range examScores {
minScore := entries[0][1]
maxScore := entries[0][1]
for _, entry := range entries {
score := entry[1]
if score < minScore {
minScore = score
}
if score > maxScore {
maxScore = score
}
}
for _, entry := range entries {
studentID := entry[0]
score := entry[1]
participated[studentID] = true
if score == minScore || score == maxScore {
disqualified[studentID] = true
}
}
}
result := [][]interface{}{}
for _, student := range students {
if participated[student.ID] &&
!disqualified[student.ID] {
result = append(result, []interface{}{
student.ID,
student.Name,
})
}
}
sort.Slice(result, func(i, j int) bool {
return result[i][0].(int) < result[j][0].(int)
})
return result
}
The Go implementation follows the same overall structure as the Python version.
Because Go does not have Python's flexible tuple support, we represent exam entries using nested integer slices.
We use maps as hash sets:
map[int]boolfor disqualified studentsmap[int]boolfor participation tracking
Sorting requires sort.Slice with a custom comparator.
Unlike Python, Go requires explicit initialization and type handling, especially when storing mixed output values in [][]interface{}.
SQL Solution
WITH exam_stats AS (
SELECT
exam_id,
MIN(score) AS min_score,
MAX(score) AS max_score
FROM Exam
GROUP BY exam_id
),
disqualified AS (
SELECT DISTINCT e.student_id
FROM Exam e
JOIN exam_stats s
ON e.exam_id = s.exam_id
WHERE e.score = s.min_score
OR e.score = s.max_score
)
SELECT
s.student_id,
s.student_name
FROM Student s
JOIN Exam e
ON s.student_id = e.student_id
WHERE s.student_id NOT IN (
SELECT student_id
FROM disqualified
)
GROUP BY s.student_id, s.student_name
ORDER BY s.student_id;
This SQL solution first computes exam boundaries using aggregation.
The disqualified CTE collects every student who ever received either the minimum or maximum score in any exam.
The final query:
- joins students with exams to ensure participation
- excludes disqualified students
- groups duplicates caused by joins
- orders results by student ID
Worked Examples
Example 1
Input:
Student table:
| student_id | student_name |
|---|---|
| 1 | Daniel |
| 2 | Jade |
| 3 | Stella |
| 4 | Jonathan |
| 5 | Will |
Exam table:
| exam_id | student_id | score |
|---|---|---|
| 10 | 1 | 70 |
| 10 | 2 | 80 |
| 10 | 3 | 90 |
| 20 | 1 | 80 |
| 30 | 1 | 70 |
| 30 | 3 | 80 |
| 30 | 4 | 90 |
| 40 | 1 | 60 |
| 40 | 2 | 70 |
| 40 | 4 | 80 |
Step 1: Group exams
| exam_id | Entries |
|---|---|
| 10 | (1,70), (2,80), (3,90) |
| 20 | (1,80) |
| 30 | (1,70), (3,80), (4,90) |
| 40 | (1,60), (2,70), (4,80) |
Step 2: Process exam 10
Scores:
- Minimum = 70
- Maximum = 90
Disqualified students:
- Student 1
- Student 3
Current sets:
| Set | Values |
|---|---|
| participated | {1,2,3} |
| disqualified | {1,3} |
Step 3: Process exam 20
Only one participant:
- Minimum = 80
- Maximum = 80
Student 1 is both minimum and maximum.
| Set | Values |
|---|---|
| participated | {1,2,3} |
| disqualified | {1,3} |
Step 4: Process exam 30
Scores:
- Minimum = 70
- Maximum = 90
Disqualified:
- Student 1
- Student 4
| Set | Values |
|---|---|
| participated | {1,2,3,4} |
| disqualified | {1,3,4} |
Step 5: Process exam 40
Scores:
- Minimum = 60
- Maximum = 80
Disqualified:
- Student 1
- Student 4
Final sets:
| Set | Values |
|---|---|
| participated | {1,2,3,4} |
| disqualified | {1,3,4} |
Step 6: Build answer
Check each student:
| Student | Participated | Disqualified | Result |
|---|---|---|---|
| 1 | Yes | Yes | Exclude |
| 2 | Yes | No | Include |
| 3 | Yes | Yes | Exclude |
| 4 | Yes | Yes | Exclude |
| 5 | No | No | Exclude |
Final answer:
| student_id | student_name |
|---|---|
| 2 | Jade |
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(E) | Each exam row is processed a constant number of times |
| Space | O(E) | Hash maps and grouping structures may store all exam rows |
The dominant cost comes from grouping exam records and scanning them to determine minimum and maximum values.
Each exam row participates in:
- grouping
- min/max calculation
- qualification checking
No nested rescanning occurs in the optimal approach, so the total runtime remains linear relative to the number of exam records.
Test Cases
from collections import defaultdict
class Solution:
def quietStudents(self, students, exams):
exam_scores = defaultdict(list)
for exam_id, student_id, score in exams:
exam_scores[exam_id].append((student_id, score))
disqualified = set()
participated = set()
for entries in exam_scores.values():
scores = [score for _, score in entries]
min_score = min(scores)
max_score = max(scores)
for student_id, score in entries:
participated.add(student_id)
if score == min_score or score == max_score:
disqualified.add(student_id)
result = []
for student_id, student_name in students:
if (
student_id in participated
and student_id not in disqualified
):
result.append([student_id, student_name])
result.sort()
return result
solver = Solution()
# Provided example
students = [
[1, "Daniel"],
[2, "Jade"],
[3, "Stella"],
[4, "Jonathan"],
[5, "Will"]
]
exams = [
[10, 1, 70],
[10, 2, 80],
[10, 3, 90],
[20, 1, 80],
[30, 1, 70],
[30, 3, 80],
[30, 4, 90],
[40, 1, 60],
[40, 2, 70],
[40, 4, 80]
]
assert solver.quietStudents(students, exams) == [
[2, "Jade"]
] # Standard example
# Single student in one exam
students = [
[1, "Alice"]
]
exams = [
[1, 1, 100]
]
assert solver.quietStudents(students, exams) == []
# Single participant is both min and max
# Two students tied for highest
students = [
[1, "A"],
[2, "B"],
[3, "C"]
]
exams = [
[1, 1, 50],
[1, 2, 100],
[1, 3, 100]
]
assert solver.quietStudents(students, exams) == []
# Students 2 and 3 are max, student 1 is min
# Student participates quietly in all exams
students = [
[1, "A"],
[2, "B"],
[3, "C"]
]
exams = [
[1, 1, 10],
[1, 2, 20],
[1, 3, 30],
[2, 1, 15],
[2, 2, 20],
[2, 3, 25]
]
assert solver.quietStudents(students, exams) == [
[2, "B"]
] # Student 2 is always middle
# Student never participates
students = [
[1, "A"],
[2, "B"]
]
exams = [
[1, 1, 100]
]
assert solver.quietStudents(students, exams) == []
# Student 2 excluded because no participation
# Multiple quiet students
students = [
[1, "A"],
[2, "B"],
[3, "C"],
[4, "D"]
]
exams = [
[1, 1, 10],
[1, 2, 20],
[1, 3, 30],
[1, 4, 25]
]
assert solver.quietStudents(students, exams) == [
[2, "B"],
[4, "D"]
] # Middle scores remain valid
| Test | Why |
|---|---|
| Provided example | Validates the core logic |
| Single student exam | Ensures one participant becomes both min and max |
| Tied highest scores | Confirms all tied extremes are disqualified |
| Consistently middle student | Verifies correct quiet student detection |
| Non-participating student | Ensures inactive students are excluded |
| Multiple quiet students | Confirms multiple valid outputs are handled |
Edge Cases
Exam with only one participant
An exam containing a single student is tricky because that student is simultaneously both the minimum and maximum scorer.
A naive implementation might incorrectly assume a student cannot be both.
Our implementation correctly handles this because the condition checks:
score == min_score or score == max_score
When both values are identical, the student is still disqualified.
Multiple students tied for highest or lowest
Some implementations incorrectly assume only one student can hold the minimum or maximum score.
However, ties are possible and every tied student must be excluded.
Our algorithm scans every participant and compares scores directly against the computed boundaries, ensuring all tied students are disqualified.
Students who never take exams
It is easy to accidentally include students who never participated because they also never became highest or lowest.
The problem explicitly excludes them.
Our implementation uses a participated set and only includes students present in that set, guaranteeing correct behavior.