Facebook Pixel

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 words
  • negative_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 student
  • student_id: An array of integers where student_id[i] is the ID of the student who received report[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:

  1. Points in descending order (highest points first)
  2. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The problem asks us to rank students based on their feedback scores, so we need to:

  1. 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 and negative_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.

  2. 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).

  3. 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.

  4. 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:

  1. 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.

  2. 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 array arr
  3. 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)
  4. 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

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 Evaluator

Example 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 and negative_feedback to sets takes O(|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) where m is the number of words in the report
    • Checking each word against the sets takes O(|s|) per word (hash set lookup), so O(m × |s|) total per student
    • Overall for all students: O(n × m × |s|)
  • Sorting the array of n students takes O(n × log n)
  • Creating the final result list takes O(k) where k ≤ 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 and ns store all positive and negative words: O((|ps| + |ns|) × |s|)
  • The arr list stores tuples for n 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) where k ≤ 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following is a good use case for backtracking?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More