Facebook Pixel

1700. Number of Students Unable to Eat Lunch

Problem Description

You have a school cafeteria serving sandwiches during lunch break. There are two types of sandwiches:

  • Circular sandwiches (represented by 0)
  • Square sandwiches (represented by 1)

The setup involves:

  • A queue of students, where each student has a preference for either circular or square sandwiches
  • A stack of sandwiches with exactly the same count as the number of students

The serving process works as follows:

  1. The student at the front of the queue looks at the sandwich on top of the stack
  2. If the student prefers that type of sandwich, they take it and leave the queue
  3. If the student doesn't want that sandwich type, they leave it there and move to the back of the queue
  4. This process continues until no remaining student in the queue wants the sandwich currently on top of the stack

You're given two arrays:

  • students: An array where students[j] represents the sandwich preference (0 or 1) of the j-th student in the initial queue, with j = 0 being the front
  • sandwiches: An array where sandwiches[i] represents the type (0 or 1) of the i-th sandwich in the stack, with i = 0 being the top

Your task is to return the number of students who are unable to eat (i.e., the students remaining in the queue when the process stops).

The key insight is that students can rotate through the queue, but the sandwich stack order is fixed. Once no student in the queue wants the current top sandwich, all remaining sandwiches below it become inaccessible.

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

Intuition

The key observation is that students can freely move to the back of the queue, meaning they can rearrange themselves in any order. However, sandwiches are stuck in their stack order - you can only access the top sandwich at any given time.

This means the problem isn't really about the order of students at all. What matters is:

  1. How many students want circular sandwiches (type 0)?
  2. How many students want square sandwiches (type 1)?

As long as there's at least one student who wants the current top sandwich, that sandwich will eventually be taken (since students keep rotating until someone who wants it reaches the front).

The process only stops when we encounter a sandwich on top that nobody in the remaining queue wants. At that point, all sandwiches below it become unreachable, and all remaining students go hungry.

So our approach becomes straightforward:

  1. Count how many students want each type of sandwich (0 or 1)
  2. Go through the sandwiches from top to bottom
  3. For each sandwich, check if there's still a student who wants that type
  4. If yes, reduce the count for that sandwich type by 1 (one student takes it and leaves)
  5. If no, we've hit a deadlock - return the count of remaining students

The beauty of this solution is that we don't need to simulate the actual queue rotation. We just need to track counts. The XOR operation v ^ 1 is a clever way to get the opposite sandwich type (if v = 0, then v ^ 1 = 1, and vice versa), which gives us the count of students who can't eat.

Learn more about Stack and Queue patterns.

Solution Approach

The solution uses a counting approach with a hash map (Counter) to track student preferences.

Step 1: Count Student Preferences

cnt = Counter(students)

We create a counter that stores how many students prefer each sandwich type. For example, if students = [1,1,0,0], then cnt would be {0: 2, 1: 2}.

Step 2: Process Sandwiches in Order

for v in sandwiches:
    if cnt[v] == 0:
        return cnt[v ^ 1]
    cnt[v] -= 1

We iterate through the sandwiches from top to bottom (index 0 to end). For each sandwich v:

  • Check availability: If cnt[v] == 0, it means no remaining students want this type of sandwich. The process stops here because:

    • This sandwich will stay on top forever
    • All sandwiches below it become inaccessible
    • We return cnt[v ^ 1], which gives the count of students who prefer the other type
  • Serve the sandwich: If cnt[v] > 0, someone wants this sandwich:

    • We decrement cnt[v] by 1 (one student takes it and leaves)
    • Move to the next sandwich in the stack

Step 3: All Sandwiches Served

return 0

If we successfully process all sandwiches without hitting a deadlock, it means every student got fed, so we return 0.

Why XOR Works The expression v ^ 1 flips between sandwich types:

  • If v = 0, then v ^ 1 = 1
  • If v = 1, then v ^ 1 = 0

This elegantly gives us the count of students who prefer the opposite type when we hit a sandwich nobody wants.

Time Complexity: O(n) where n is the number of sandwiches Space Complexity: O(1) since the counter only stores at most 2 keys (sandwich types 0 and 1)

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through an example with students = [1,1,1,0,0,1] and sandwiches = [1,0,0,0,1,1].

Step 1: Count Student Preferences We count how many students want each type:

  • Students wanting circular (0): 2 students
  • Students wanting square (1): 4 students
  • Counter: {0: 2, 1: 4}

Step 2: Process Sandwiches

Sandwich 1 (type 1 - square):

  • Check: Are there students who want square? Yes, cnt[1] = 4
  • Serve it: cnt[1] becomes 3
  • Counter: {0: 2, 1: 3}

Sandwich 2 (type 0 - circular):

  • Check: Are there students who want circular? Yes, cnt[0] = 2
  • Serve it: cnt[0] becomes 1
  • Counter: {0: 1, 1: 3}

Sandwich 3 (type 0 - circular):

  • Check: Are there students who want circular? Yes, cnt[0] = 1
  • Serve it: cnt[0] becomes 0
  • Counter: {0: 0, 1: 3}

Sandwich 4 (type 0 - circular):

  • Check: Are there students who want circular? No, cnt[0] = 0
  • STOP! No student wants this circular sandwich
  • The 3 remaining students all want square sandwiches
  • Return cnt[0 ^ 1] = cnt[1] = 3

Result: 3 students unable to eat

The key insight: Once we hit sandwich 4 (circular) and no one wants it, this sandwich stays on top forever. The 2 square sandwiches below it (positions 5 and 6) become unreachable, even though there are 3 students who want square sandwiches.

Solution Implementation

1from typing import List
2from collections import Counter
3
4class Solution:
5    def countStudents(self, students: List[int], sandwiches: List[int]) -> int:
6        # Count the number of students preferring each type of sandwich (0 or 1)
7        student_preferences = Counter(students)
8      
9        # Process sandwiches from top to bottom
10        for sandwich_type in sandwiches:
11            # If no students want the current sandwich type, we're stuck
12            if student_preferences[sandwich_type] == 0:
13                # Return the count of students wanting the other type (XOR flips 0->1, 1->0)
14                return student_preferences[sandwich_type ^ 1]
15          
16            # A student takes the sandwich, decrease the count
17            student_preferences[sandwich_type] -= 1
18      
19        # All sandwiches were taken, no students left waiting
20        return 0
21
1class Solution {
2    public int countStudents(int[] students, int[] sandwiches) {
3        // Count the number of students preferring each type of sandwich
4        // count[0] = number of students preferring circular sandwiches (0)
5        // count[1] = number of students preferring square sandwiches (1)
6        int[] count = new int[2];
7      
8        // Count preferences of all students
9        for (int preference : students) {
10            count[preference]++;
11        }
12      
13        // Process sandwiches from top to bottom
14        for (int sandwichType : sandwiches) {
15            // Check if there are students who want this type of sandwich
16            if (count[sandwichType] == 0) {
17                // No students want this sandwich type
18                // Return the count of students preferring the other type
19                // XOR with 1 flips the bit: 0^1=1, 1^1=0
20                return count[sandwichType ^ 1];
21            }
22            // A student takes this sandwich
23            count[sandwichType]--;
24        }
25      
26        // All sandwiches were taken
27        return 0;
28    }
29}
30
1class Solution {
2public:
3    int countStudents(vector<int>& students, vector<int>& sandwiches) {
4        // Count the number of students preferring each type of sandwich
5        // count[0]: number of students preferring circular sandwiches (0)
6        // count[1]: number of students preferring square sandwiches (1)
7        int count[2] = {0};
8      
9        // Count preferences of all students
10        for (int& preference : students) {
11            ++count[preference];
12        }
13      
14        // Process sandwiches in order from top of stack
15        for (int& sandwichType : sandwiches) {
16            // Check if there are students who want this type of sandwich
17            if (count[sandwichType] == 0) {
18                // No student wants this sandwich type
19                // Return the number of students wanting the other type
20                // XOR with 1 flips the bit: 0^1=1, 1^1=0
21                return count[sandwichType ^ 1];
22            }
23            // A student takes this sandwich
24            --count[sandwichType];
25        }
26      
27        // All sandwiches were taken
28        return 0;
29    }
30};
31
1/**
2 * Counts the number of students who cannot eat lunch
3 * @param students - Array where students[i] is 0 (prefers circular sandwich) or 1 (prefers square sandwich)
4 * @param sandwiches - Array where sandwiches[i] is 0 (circular) or 1 (square), served from top to bottom
5 * @returns Number of students who cannot eat
6 */
7function countStudents(students: number[], sandwiches: number[]): number {
8    // Count the number of students who prefer each type of sandwich
9    // studentPreferences[0] = count of students who prefer circular (0)
10    // studentPreferences[1] = count of students who prefer square (1)
11    const studentPreferences: number[] = [0, 0];
12  
13    // Count preferences for each sandwich type
14    for (const studentPreference of students) {
15        studentPreferences[studentPreference]++;
16    }
17  
18    // Process sandwiches in order from top to bottom
19    for (const sandwichType of sandwiches) {
20        // If no students want this type of sandwich, 
21        // all remaining students want the other type
22        if (studentPreferences[sandwichType] === 0) {
23            // Use XOR (^) to flip between 0 and 1
24            // If sandwichType is 0, return count of type 1 students
25            // If sandwichType is 1, return count of type 0 students
26            return studentPreferences[sandwichType ^ 1];
27        }
28      
29        // A student takes this sandwich
30        studentPreferences[sandwichType]--;
31    }
32  
33    // All students got their sandwiches
34    return 0;
35}
36

Time and Space Complexity

The time complexity is O(n), where n is the number of sandwiches. The algorithm iterates through the sandwiches list once, and for each sandwich, it performs constant-time operations (checking if a count exists in the Counter and decrementing it).

The space complexity is O(1). Although we use a Counter to store student preferences, there are only two possible values for student preferences (0 or 1), so the Counter will contain at most 2 key-value pairs regardless of the input size. This makes the space usage constant.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Trying to Simulate the Actual Queue Rotation

Many developers initially attempt to simulate the actual queue rotation process by moving students to the back of the queue when they don't match the top sandwich. This leads to unnecessary complexity and potential infinite loop scenarios.

Incorrect Approach:

def countStudents(self, students: List[int], sandwiches: List[int]) -> int:
    students = deque(students)
    sandwich_idx = 0
    rotations = 0
  
    while students and rotations < len(students):
        if students[0] == sandwiches[sandwich_idx]:
            students.popleft()
            sandwich_idx += 1
            rotations = 0
        else:
            students.rotate(-1)
            rotations += 1
  
    return len(students)

Why it's problematic:

  • More complex logic to track when to stop (rotation counter needed)
  • Higher time complexity due to actual queue operations
  • Harder to debug and reason about

Solution: Use the counting approach shown in the main solution - it abstracts away the rotation mechanics since we only care about whether enough students of each preference type exist.

Pitfall 2: Misunderstanding When the Process Stops

Some might think the process stops only when all students have cycled through once. In reality, it stops when no student in the entire queue wants the current top sandwich, regardless of how many times they've cycled.

Incorrect Logic:

def countStudents(self, students: List[int], sandwiches: List[int]) -> int:
    cnt = Counter(students)
    for i, sandwich in enumerate(sandwiches):
        if cnt[sandwich] > 0:
            cnt[sandwich] -= 1
        # Wrong: continuing even when nobody wants this sandwich
    return sum(cnt.values())

Solution: Always check if cnt[sandwich] == 0 before trying to serve. If true, immediately return the count of remaining students since all subsequent sandwiches are inaccessible.

Pitfall 3: Off-by-One Errors with XOR Operation

Some developers might not be familiar with using XOR to flip between 0 and 1, leading to verbose or incorrect alternative implementations.

Verbose Alternative:

if cnt[v] == 0:
    if v == 0:
        return cnt[1]
    else:
        return cnt[0]

Potential Error:

if cnt[v] == 0:
    return cnt[1 - v]  # Works but less elegant
    # Or worse:
    return cnt[~v]  # Wrong! ~0 is -1, not 1

Solution: Use v ^ 1 for clean bit flipping between 0 and 1. It's concise and clearly indicates the intent to get the "other" type.

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

How many times is a tree node visited in a depth first search?


Recommended Readings

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

Load More