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:
- The student at the front of the queue looks at the sandwich on top of the stack
- If the student prefers that type of sandwich, they take it and leave the queue
- If the student doesn't want that sandwich type, they leave it there and move to the back of the queue
- 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 wherestudents[j]
represents the sandwich preference (0 or 1) of thej
-th student in the initial queue, withj = 0
being the frontsandwiches
: An array wheresandwiches[i]
represents the type (0 or 1) of thei
-th sandwich in the stack, withi = 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.
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:
- How many students want circular sandwiches (type 0)?
- 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:
- Count how many students want each type of sandwich (0 or 1)
- Go through the sandwiches from top to bottom
- For each sandwich, check if there's still a student who wants that type
- If yes, reduce the count for that sandwich type by 1 (one student takes it and leaves)
- 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.
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
- We decrement
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
, thenv ^ 1 = 1
- If
v = 1
, thenv ^ 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 EvaluatorExample 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.
How many times is a tree node visited in a depth first search?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Queue Intro Think of the last time you stood in line to buy movie tickets The first person in line gets their ticket first and any newcomers join the end of the line This system where the first person to arrive is the first to be served is a queue in real
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!