2512. Reward Top K Students
Problem Description
You need to rank students based on feedback reports containing positive and negative words.
Input:
positive_feedback
: An array of strings representing positive wordsnegative_feedback
: An array of strings representing negative words (no word appears in both arrays)report
: An array of strings where each string is a feedback report for a studentstudent_id
: An array of integers wherestudent_id[i]
is the ID of the student who receivedreport[i]
k
: An integer representing how many top students to return
Scoring Rules:
- Each student starts with 0 points
- Each positive word in their feedback report adds 3 points
- Each negative word in their feedback report subtracts 1 point
Task:
Return the IDs of the top k
students ranked by:
- Points in descending order (highest points first)
- If points are equal, rank by student ID in ascending order (lower ID ranks higher)
Example:
If a student's report contains "good excellent bad", and "good" and "excellent" are positive words while "bad" is negative, the student would score: 3 + 3 - 1 = 5
points.
The solution uses hash sets to store positive and negative words for O(1) lookup, calculates each student's score by parsing their report, stores the results as (score, student_id)
tuples, sorts them according to the ranking criteria, and returns the top k
student IDs.
Intuition
The problem asks us to rank students based on their feedback scores, so we need to:
-
Calculate each student's score efficiently: Since we need to check if each word in a report is positive or negative, using hash sets for
positive_feedback
andnegative_feedback
gives us O(1) lookup time per word. This is much better than searching through arrays which would take O(n) time for each word. -
Process multiple reports: We need to go through each student's report, split it into individual words, and check each word against our positive and negative word sets. For each word match, we update the running score accordingly (
+3
for positive,-1
for negative). -
Handle the ranking criteria: The problem has a two-level sorting requirement:
- Primary: Sort by points (highest first)
- Secondary: Sort by student ID (lowest first) when points are equal
We can achieve this by storing each student's data as a tuple
(score, student_id)
and using a custom sort key. By sorting with(-score, student_id)
, we get descending order for scores (the negative sign reverses the order) and ascending order for IDs. -
Return top k students: After sorting all students according to the ranking criteria, we simply take the first
k
elements and extract their student IDs.
The key insight is that we don't need complex data structures - just efficient lookups for word checking (hash sets), a simple way to store score-ID pairs (tuples), and Python's built-in sorting with a custom key function to handle the ranking rules.
Learn more about Sorting and Heap (Priority Queue) patterns.
Solution Approach
The solution follows these implementation steps:
-
Create Hash Sets for Word Lookup
ps = set(positive_feedback) ns = set(negative_feedback)
Convert both feedback arrays into sets for O(1) lookup time when checking if a word is positive or negative.
-
Calculate Scores for Each Student
arr = [] for sid, r in zip(student_id, report): t = 0 for w in r.split(): if w in ps: t += 3 elif w in ns: t -= 1 arr.append((t, sid))
- Use
zip()
to iterate through student IDs and their corresponding reports simultaneously - For each report, split it into individual words using
split()
- Check each word against the positive and negative sets
- Accumulate the score
t
: add 3 for positive words, subtract 1 for negative words - Store the result as a tuple
(score, student_id)
in the arrayarr
- Use
-
Sort Students by Ranking Criteria
arr.sort(key=lambda x: (-x[0], x[1]))
Sort the array using a custom key function:
-x[0]
: Negative score ensures descending order (highest scores first)x[1]
: Student ID in ascending order (lower IDs rank higher for equal scores)
-
Extract Top k Student IDs
return [v[1] for v in arr[:k]]
- Slice the first
k
elements from the sorted array - Use list comprehension to extract only the student IDs (index 1 of each tuple)
- Return the resulting list of top k student IDs
- Slice the first
The time complexity is O(n × m + n log n) where n is the number of students and m is the average number of words per report. The space complexity is O(p + q + n) where p and q are the sizes of positive and negative feedback arrays.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach:
Given:
positive_feedback = ["smart", "brilliant", "studious"]
negative_feedback = ["lazy", "slow"]
report = ["this student is brilliant and smart", "lazy but studious", "slow learner"]
student_id = [1, 5, 2]
k = 2
Step 1: Create Hash Sets
ps = {smart, brilliant, studious} ns = {lazy, slow}
Step 2: Calculate Scores for Each Student
Student 1 (report: "this student is brilliant and smart"):
- "this" → not in ps or ns → score: 0
- "student" → not in ps or ns → score: 0
- "is" → not in ps or ns → score: 0
- "brilliant" → in ps → score: 0 + 3 = 3
- "and" → not in ps or ns → score: 3
- "smart" → in ps → score: 3 + 3 = 6
- Store: (6, 1)
Student 5 (report: "lazy but studious"):
- "lazy" → in ns → score: 0 - 1 = -1
- "but" → not in ps or ns → score: -1
- "studious" → in ps → score: -1 + 3 = 2
- Store: (2, 5)
Student 2 (report: "slow learner"):
- "slow" → in ns → score: 0 - 1 = -1
- "learner" → not in ps or ns → score: -1
- Store: (-1, 2)
Array after scoring: arr = [(6, 1), (2, 5), (-1, 2)]
Step 3: Sort by Ranking Criteria
Apply sorting with key (-score, student_id)
:
- (6, 1) → key: (-6, 1)
- (2, 5) → key: (-2, 5)
- (-1, 2) → key: (1, 2)
Sorted order: [(6, 1), (2, 5), (-1, 2)]
Step 4: Extract Top k=2 Student IDs
Take first 2 elements: [(6, 1), (2, 5)]
Extract IDs: [1, 5]
Final Answer: [1, 5]
The top 2 students are Student 1 (6 points) and Student 5 (2 points).
Solution Implementation
1class Solution:
2 def topStudents(
3 self,
4 positive_feedback: List[str],
5 negative_feedback: List[str],
6 report: List[str],
7 student_id: List[int],
8 k: int,
9 ) -> List[int]:
10 # Convert feedback lists to sets for O(1) lookup
11 positive_words = set(positive_feedback)
12 negative_words = set(negative_feedback)
13
14 # Store (score, student_id) pairs for each student
15 student_scores = []
16
17 # Calculate score for each student based on their report
18 for student_id_value, student_report in zip(student_id, report):
19 score = 0
20
21 # Split report into words and calculate score
22 for word in student_report.split():
23 if word in positive_words:
24 score += 3 # Add 3 points for positive feedback words
25 elif word in negative_words:
26 score -= 1 # Subtract 1 point for negative feedback words
27
28 # Store the score with student ID
29 student_scores.append((score, student_id_value))
30
31 # Sort by score (descending) and then by student ID (ascending)
32 student_scores.sort(key=lambda x: (-x[0], x[1]))
33
34 # Extract and return the top k student IDs
35 return [student_info[1] for student_info in student_scores[:k]]
36
1class Solution {
2 public List<Integer> topStudents(String[] positive_feedback, String[] negative_feedback,
3 String[] report, int[] student_id, int k) {
4
5 // Create sets for O(1) lookup of positive and negative feedback words
6 Set<String> positiveWords = new HashSet<>();
7 Set<String> negativeWords = new HashSet<>();
8
9 // Populate the positive feedback word set
10 for (String word : positive_feedback) {
11 positiveWords.add(word);
12 }
13
14 // Populate the negative feedback word set
15 for (String word : negative_feedback) {
16 negativeWords.add(word);
17 }
18
19 // Get the number of students
20 int numberOfStudents = report.length;
21
22 // Create array to store [score, studentId] pairs
23 int[][] studentScores = new int[numberOfStudents][2];
24
25 // Calculate score for each student
26 for (int i = 0; i < numberOfStudents; i++) {
27 int currentStudentId = student_id[i];
28 int score = 0;
29
30 // Split the report into individual words and calculate score
31 for (String word : report[i].split(" ")) {
32 if (positiveWords.contains(word)) {
33 score += 3; // Add 3 points for positive feedback word
34 } else if (negativeWords.contains(word)) {
35 score -= 1; // Subtract 1 point for negative feedback word
36 }
37 }
38
39 // Store the score and student ID
40 studentScores[i] = new int[] {score, currentStudentId};
41 }
42
43 // Sort students by score (descending), then by ID (ascending) for tie-breaking
44 Arrays.sort(studentScores, (a, b) -> {
45 if (a[0] == b[0]) {
46 return a[1] - b[1]; // If scores are equal, sort by student ID ascending
47 }
48 return b[0] - a[0]; // Sort by score descending
49 });
50
51 // Build the result list with top k student IDs
52 List<Integer> topKStudents = new ArrayList<>();
53 for (int i = 0; i < k; i++) {
54 topKStudents.add(studentScores[i][1]);
55 }
56
57 return topKStudents;
58 }
59}
60
1class Solution {
2public:
3 vector<int> topStudents(vector<string>& positive_feedback, vector<string>& negative_feedback,
4 vector<string>& report, vector<int>& student_id, int k) {
5 // Convert feedback vectors to hash sets for O(1) lookup
6 unordered_set<string> positiveWords(positive_feedback.begin(), positive_feedback.end());
7 unordered_set<string> negativeWords(negative_feedback.begin(), negative_feedback.end());
8
9 // Store pairs of (negative score, student id) for sorting
10 // Using negative score to sort in descending order
11 vector<pair<int, int>> scoreStudentPairs;
12
13 int numStudents = report.size();
14
15 // Calculate score for each student
16 for (int i = 0; i < numStudents; ++i) {
17 int currentStudentId = student_id[i];
18
19 // Split the report into individual words
20 vector<string> words = splitString(report[i], ' ');
21
22 // Calculate total score for this student
23 int totalScore = 0;
24 for (const auto& word : words) {
25 if (positiveWords.count(word)) {
26 totalScore += 3; // Positive feedback adds 3 points
27 } else if (negativeWords.count(word)) {
28 totalScore -= 1; // Negative feedback subtracts 1 point
29 }
30 }
31
32 // Store negative score for descending order sort, and student id
33 scoreStudentPairs.push_back({-totalScore, currentStudentId});
34 }
35
36 // Sort by score (descending) and student id (ascending)
37 // Since we stored negative scores, standard sort will give us descending order
38 sort(scoreStudentPairs.begin(), scoreStudentPairs.end());
39
40 // Extract top k student ids
41 vector<int> result;
42 for (int i = 0; i < k; ++i) {
43 result.emplace_back(scoreStudentPairs[i].second);
44 }
45
46 return result;
47 }
48
49private:
50 // Helper function to split a string by delimiter
51 vector<string> splitString(const string& str, char delimiter) {
52 stringstream stringStream(str);
53 string token;
54 vector<string> tokens;
55
56 // Extract tokens separated by delimiter
57 while (getline(stringStream, token, delimiter)) {
58 tokens.emplace_back(token);
59 }
60
61 return tokens;
62 }
63};
64
1/**
2 * Finds the top k students based on their feedback scores
3 * @param positive_feedback - Array of positive feedback words (worth +3 points each)
4 * @param negative_feedback - Array of negative feedback words (worth -1 point each)
5 * @param report - Array of feedback reports for each student
6 * @param student_id - Array of student IDs corresponding to each report
7 * @param k - Number of top students to return
8 * @returns Array of top k student IDs sorted by score (descending) and ID (ascending for ties)
9 */
10function topStudents(
11 positive_feedback: string[],
12 negative_feedback: string[],
13 report: string[],
14 student_id: number[],
15 k: number,
16): number[] {
17 // Get the total number of students
18 const studentCount: number = student_id.length;
19
20 // Map to store student ID to score mapping
21 const studentScoreMap: Map<number, number> = new Map<number, number>();
22
23 // Convert feedback arrays to sets for O(1) lookup
24 const positiveFeedbackSet: Set<string> = new Set(positive_feedback);
25 const negativeFeedbackSet: Set<string> = new Set(negative_feedback);
26
27 // Calculate score for each student based on their report
28 for (let i = 0; i < studentCount; i++) {
29 // Split the report into individual words
30 const words: string[] = report[i].split(' ');
31
32 // Calculate the total score for this student's report
33 const totalScore: number = words.reduce((accumulatedScore: number, word: string) => {
34 // Add 3 points for positive feedback words
35 if (positiveFeedbackSet.has(word)) {
36 return accumulatedScore + 3;
37 }
38 // Subtract 1 point for negative feedback words
39 if (negativeFeedbackSet.has(word)) {
40 return accumulatedScore - 1;
41 }
42 // No change for neutral words
43 return accumulatedScore;
44 }, 0);
45
46 // Store the calculated score for this student
47 studentScoreMap.set(student_id[i], totalScore);
48 }
49
50 // Convert map entries to array and sort by score (descending) and ID (ascending)
51 const sortedStudentEntries: [number, number][] = [...studentScoreMap.entries()]
52 .sort((entryA: [number, number], entryB: [number, number]) => {
53 // If scores are equal, sort by student ID in ascending order
54 if (entryA[1] === entryB[1]) {
55 return entryA[0] - entryB[0];
56 }
57 // Otherwise, sort by score in descending order
58 return entryB[1] - entryA[1];
59 });
60
61 // Extract student IDs and return top k students
62 return sortedStudentEntries
63 .map((entry: [number, number]) => entry[0])
64 .slice(0, k);
65}
66
Time and Space Complexity
Time Complexity: O(n × log n + n × m)
- Converting
positive_feedback
andnegative_feedback
to sets takesO(|ps| × |s| + |ns| × |s|)
where|ps|
is the number of positive words,|ns|
is the number of negative words, and|s|
is the average length of a word. - The main loop iterates through
n
students, and for each student:- Splitting the report string takes
O(m)
wherem
is the number of words in the report - Checking each word against the sets takes
O(|s|)
per word (hash set lookup), soO(m × |s|)
total per student - Overall for all students:
O(n × m × |s|)
- Splitting the report string takes
- Sorting the array of
n
students takesO(n × log n)
- Creating the final result list takes
O(k)
wherek ≤ n
Combined: O((|ps| + |ns|) × |s| + n × m × |s| + n × log n)
, which simplifies to O(n × log n + (|ps| + |ns| + n × m) × |s|)
. When considering m
as part of the total words across all reports (making it equivalent to n
in terms of total processing), this becomes O(n × log n + (|ps| + |ns| + n) × |s|)
.
Space Complexity: O((|ps| + |ns|) × |s| + n)
- The sets
ps
andns
store all positive and negative words:O((|ps| + |ns|) × |s|)
- The
arr
list stores tuples forn
students:O(n)
- The split operation creates temporary lists but only one at a time:
O(m)
which is dominated by other terms - The final result list stores at most
k
student IDs:O(k)
wherek ≤ n
Combined: O((|ps| + |ns|) × |s| + n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Word Boundary Issues with Substring Matching
A critical mistake is checking if positive/negative words are substrings of the report rather than exact word matches:
Incorrect Approach:
# Wrong - this checks for substrings for word in positive_feedback: if word in report[i]: # This matches "good" in "goodness" score += 3
Why it's wrong: If "good" is a positive word and the report contains "goodness", this would incorrectly add 3 points since "good" is a substring of "goodness".
Correct Solution:
# Split the report into individual words first for word in student_report.split(): if word in positive_words: score += 3
2. Incorrect Sorting Order
Mixing up the sorting criteria is a frequent error:
Incorrect Approach:
# Wrong - sorts scores in ascending order student_scores.sort(key=lambda x: (x[0], x[1])) # Or wrong - sorts IDs in descending order for ties student_scores.sort(key=lambda x: (-x[0], -x[1]))
Correct Solution:
# Scores descending (highest first), IDs ascending (lowest first for ties) student_scores.sort(key=lambda x: (-x[0], x[1]))
3. Case Sensitivity Issues
The problem doesn't specify case handling, but assuming case-sensitive matching when it should be case-insensitive (or vice versa) can cause errors:
Potential Issue:
# If "Good" in report but "good" in positive_feedback if word in positive_words: # Won't match due to case difference
Solution if case-insensitive matching is needed:
# Convert everything to lowercase
positive_words = set(word.lower() for word in positive_feedback)
negative_words = set(word.lower() for word in negative_feedback)
for word in student_report.split():
word_lower = word.lower()
if word_lower in positive_words:
score += 3
elif word_lower in negative_words:
score -= 1
4. Not Handling Edge Cases
Failing to consider edge cases like empty reports or k larger than the number of students:
Potential Issues:
- Empty report strings that cause unexpected behavior
- k > len(student_id) causing index errors
Robust Solution:
# Handle empty reports gracefully
for word in student_report.split(): # split() on empty string returns []
# Process normally
# Handle k larger than number of students
return [student_info[1] for student_info in student_scores[:min(k, len(student_scores))]]
5. Using Lists Instead of Sets for Lookup
Using the original lists for word lookup instead of converting to sets:
Incorrect (Inefficient) Approach:
# O(n) lookup time for each word check if word in positive_feedback: # positive_feedback is a list score += 3
Correct Solution:
# O(1) lookup time with sets
positive_words = set(positive_feedback)
if word in positive_words:
score += 3
This increases time complexity from O(n × m × p) to O(n × m) where p is the size of the feedback arrays.
Which of the following is a good use case for backtracking?
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!