1700. Number of Students Unable to Eat Lunch


Problem Description

In this problem, we have a queue of students and a stack of sandwiches. The sandwiches can be of two types, represented by 0 (circular) and 1 (square). Each student in the queue has a preference for either circular or square sandwiches.

Students are served in the order they are standing in the queue. If the student at the front of the queue prefers the type of sandwich that is on top of the sandwich stack, they take the sandwich and leave the queue. However, if they do not prefer the type of sandwich on top of the stack, they pass on the sandwich and move to the end of the queue.

The process stops when the sandwich on the top of the stack is not preferred by any of the students left in the queue, meaning no more sandwiches can be served, and some students will be unable to eat. The task is to find the number of students who will not be able to eat a sandwich.

Intuition

The key insight into solving this problem lies in recognizing that if a certain type of sandwich could not be taken by any student in the queue, no further progress can be made. This is because the students' order and preferences do not change.

We start with two counts: how many students prefer circular sandwiches and how many prefer square ones. We process the sandwich stack from the top (i = 0 being the top of the stack). For each sandwich, we check if there is a student in the queue who prefers that type of sandwich. If there is, we decrease the count of students waiting for that type of sandwich. If there isn't, it means all remaining students prefer the other type of sandwich and will be unable to eat. At this point, we can return the number of students who prefer the other type of sandwich.

This approach ensures we check the selection possibility for each sandwich without actually moving students around in the queue, which results in a time-efficient solution.

The solution implementation is straightforward: we keep counts of students' preferences and iterate through the sandwich stack. When we encounter a sandwich with no matching student preference, we stop and return the count of the other type of sandwich. If we go through the whole stack, it means all students got their preferred sandwich, and we return 0.

Learn more about Stack and Queue patterns.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece:

What's the output of running the following function using input 56?

1KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13    def dfs(path, res):
14        if len(path) == len(digits):
15            res.append(''.join(path))
16            return
17
18        next_number = digits[len(path)]
19        for letter in KEYBOARD[next_number]:
20            path.append(letter)
21            dfs(path, res)
22            path.pop()
23
24    res = []
25    dfs([], res)
26    return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2    '2', "abc".toCharArray(),
3    '3', "def".toCharArray(),
4    '4', "ghi".toCharArray(),
5    '5', "jkl".toCharArray(),
6    '6', "mno".toCharArray(),
7    '7', "pqrs".toCharArray(),
8    '8', "tuv".toCharArray(),
9    '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13    List<String> res = new ArrayList<>();
14    dfs(new StringBuilder(), res, digits.toCharArray());
15    return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19    if (path.length() == digits.length) {
20        res.add(path.toString());
21        return;
22    }
23    char next_digit = digits[path.length()];
24    for (char letter : KEYBOARD.get(next_digit)) {
25        path.append(letter);
26        dfs(path, res, digits);
27        path.deleteCharAt(path.length() - 1);
28    }
29}
30
1const KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13    let res = [];
14    dfs(digits, [], res);
15    return res;
16}
17
18function dfs(digits, path, res) {
19    if (path.length === digits.length) {
20        res.push(path.join(''));
21        return;
22    }
23    let next_number = digits.charAt(path.length);
24    for (let letter of KEYBOARD[next_number]) {
25        path.push(letter);
26        dfs(digits, path, res);
27        path.pop();
28    }
29}
30

Solution Approach

The solution provided uses the Counter class from Python's collections module to efficiently keep track of the count of students' sandwich preferences.

Here's a step-by-step explanation of the implementation:

  1. A Counter object called cnt is created from the students list. This will give us a dictionary-like object where the keys are 0 and 1, and the values are the count of students preferring circular (0) and square (1) sandwiches respectively.

  2. The solution then iterates over the sandwiches stack with a for-loop. For each sandwich type v at the top of the stack:

    a. The solution checks if cnt[v] equals 0. If it is, this means there are no students left that prefer the current type of sandwich on top of the stack. Since the students who prefer the other type of sandwich won't take the current one, the solution returns cnt[v ^ 1]. The expression v ^ 1 is a bitwise XOR operation that toggles between 0 and 1, effectively giving us the count of the other sandwich preference.

    b. If cnt[v] does not equal 0, which means there is at least one student who prefers the type of sandwich on top of the stack, the solution decrements the count of that sandwich preference by 1 since that student takes the sandwich and leaves the queue.

  3. After the for-loop, if all sandwiches have been successfully served to students, the final return value is 0, meaning no students are left unable to eat.

The for-loop in this approach works effectively because it processes the sandwiches in the natural order they are being served, and the Counter keeps track of the dynamic preference count without the need to manage the queue of students manually.

By running this efficient loop and conditions, the algorithm ensures a linear time complexity proportional to the number of sandwiches (or equivalently the number of students), which is O(n) where n is the number of students or the length of the sandwiches array.

Therefore, the algorithm and the usage of the Counter data structure work together to provide a solution that is straightforward, readable, and efficient in both time and space.

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

What's the output of running the following function using input [30, 20, 10, 100, 33, 12]?

1def fun(arr: List[int]) -> List[int]:
2    import heapq
3    heapq.heapify(arr)
4    res = []
5    for i in range(3):
6        res.append(heapq.heappop(arr))
7    return res
8
1public static int[] fun(int[] arr) {
2    int[] res = new int[3];
3    PriorityQueue<Integer> heap = new PriorityQueue<>();
4    for (int i = 0; i < arr.length; i++) {
5        heap.add(arr[i]);
6    }
7    for (int i = 0; i < 3; i++) {
8        res[i] = heap.poll();
9    }
10    return res;
11}
12
1class HeapItem {
2    constructor(item, priority = item) {
3        this.item = item;
4        this.priority = priority;
5    }
6}
7
8class MinHeap {
9    constructor() {
10        this.heap = [];
11    }
12
13    push(node) {
14        // insert the new node at the end of the heap array
15        this.heap.push(node);
16        // find the correct position for the new node
17        this.bubble_up();
18    }
19
20    bubble_up() {
21        let index = this.heap.length - 1;
22
23        while (index > 0) {
24            const element = this.heap[index];
25            const parentIndex = Math.floor((index - 1) / 2);
26            const parent = this.heap[parentIndex];
27
28            if (parent.priority <= element.priority) break;
29            // if the parent is bigger than the child then swap the parent and child
30            this.heap[index] = parent;
31            this.heap[parentIndex] = element;
32            index = parentIndex;
33        }
34    }
35
36    pop() {
37        const min = this.heap[0];
38        this.heap[0] = this.heap[this.size() - 1];
39        this.heap.pop();
40        this.bubble_down();
41        return min;
42    }
43
44    bubble_down() {
45        let index = 0;
46        let min = index;
47        const n = this.heap.length;
48
49        while (index < n) {
50            const left = 2 * index + 1;
51            const right = left + 1;
52
53            if (left < n && this.heap[left].priority < this.heap[min].priority) {
54                min = left;
55            }
56            if (right < n && this.heap[right].priority < this.heap[min].priority) {
57                min = right;
58            }
59            if (min === index) break;
60            [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61            index = min;
62        }
63    }
64
65    peek() {
66        return this.heap[0];
67    }
68
69    size() {
70        return this.heap.length;
71    }
72}
73
74function fun(arr) {
75    const heap = new MinHeap();
76    for (const x of arr) {
77        heap.push(new HeapItem(x));
78    }
79    const res = [];
80    for (let i = 0; i < 3; i++) {
81        res.push(heap.pop().item);
82    }
83    return res;
84}
85

Example Walkthrough

Let's consider a small example to illustrate the solution approach:

Suppose we have the following queue of students and stack of sandwiches:

1Students preferences (queue): [1, 0, 0, 1, 1]
2Sandwiches stack (top to bottom): [0, 1, 0, 1, 1]

Each 1 in the students' queue represents a student who prefers square sandwiches and each 0 represents a student who prefers circular sandwiches.

Step 1:

  • We initialize a Counter object from the students' preferences: cnt = Counter([1, 0, 0, 1, 1]), resulting in cnt = {0: 2, 1: 3}. This means 2 students prefer circular sandwiches and 3 prefer square ones.

Step 2:

  • Now, we start iterating over the sandwiches stack from the top:

Iteration 1:

  • The top of the stack is a circular sandwich (0). cnt[0] is not 0, so there's a student who prefers this sandwich.
  • We decrement cnt[0] by 1, leaving cnt as {0: 1, 1: 3}.

Iteration 2:

  • Next sandwich is square (1). cnt[1] is not 0, indicating a matching student preference.
  • We decrement cnt[1] by 1, making cnt {0: 1, 1: 2}.

Iteration 3:

  • Another circular sandwich (0) is on top. cnt[0] is not 0, so a student will take this sandwich.
  • We decrement cnt[0] by 1 and now cnt is {0: 0, 1: 2}.

Iteration 4:

  • The next sandwich is square (1). cnt[1] is not 0, so a student prefers and takes it.
  • We decrement cnt[1] to 1, updating cnt to {0: 0, 1: 1}.

Iteration 5:

  • The last sandwich is also square (1). cnt[1] is not 0.
  • We decrement cnt[1] to 0, final cnt is {0: 0, 1: 0}.

Step 3:

  • We have successfully iterated over all the sandwiches, and the cnt for both types of sandwiches is 0, meaning each student got their preference.

Final step:

  • The final return value is 0 since no students are left unable to eat.

This small example demonstrates how we can efficiently determine that all students will be able to eat by just keeping track of the counts of their sandwich preferences and decrementing the respective counts as we match students with sandwiches. No actual rearrangement of the queue is required. If at any point we discovered a sandwich on top that matches none of the students' remaining preferences, we would return the number of students left with the non-matching preference immediately.

Not Sure What to Study? Take the 2-min Quiz:

Suppose k is a very large integer(2^64). Which of the following is the largest as n grows to infinity?

Python Solution

1from collections import Counter
2
3class Solution:
4    def countStudents(self, students: List[int], sandwiches: List[int]) -> int:
5        # Count the number of students preferring each type of sandwich
6        count = Counter(students)
7      
8        # Loop through the sandwiches queue
9        for sandwich in sandwiches:
10            # If no student wants the current type of sandwich, 
11            # return the number of students who want the other type
12            if count[sandwich] == 0:
13                # The 'sandwich ^ 1' toggles between 1 and 0
14                return count[sandwich ^ 1]
15          
16            # Decrement the count for the current sandwich type
17            count[sandwich] -= 1
18      
19        # If all sandwiches match the students' preferences return 0 
20        return 0
21

Java Solution

1class Solution {
2    public int countStudents(int[] students, int[] sandwiches) {
3        // Array to count the number of students who prefer each type of sandwich
4        // cnt[0] for students who prefer sandwich type 0, and cnt[1] for sandwich type 1
5        int[] count = new int[2];
6      
7        // Count the number of students preferring each type of sandwich
8        for (int student : students) {
9            count[student]++;
10        }
11      
12        // Iterate through the sandwiches
13        for (int sandwich : sandwiches) {
14            // If no students want the current type of sandwich, break and return the number of students
15            // who want the other type of sandwich
16            if (count[sandwich] == 0) {
17                // The other type is obtained by XORing the current type with 1
18                // If current is 0, 0^1 will be 1 (the other type)
19                // If current is 1, 1^1 will be 0 (the other type)
20                return count[sandwich ^ 1];
21            }
22            // Decrement the count as one student takes a sandwich of the type they prefer
23            count[sandwich]--;
24        }
25      
26        // If all students got their preferred sandwiches, return 0
27        return 0;
28    }
29}
30

C++ Solution

1#include <vector> // make sure to include the vector header for vector support
2
3class Solution {
4public:
5    // Function to count the number of students who can't get a sandwich
6    int countStudents(vector<int>& students, vector<int>& sandwiches) {
7        // Initialize a count array to store the number of students preferring each type of sandwich
8        int count[2] = {0};
9      
10        // Count the number of students preferring each type of sandwich (0 or 1)
11        for (int student : students) {
12            count[student]++;
13        }
14      
15        // Iterate through the sandwiches queue
16        for (int sandwich : sandwiches) {
17            // If no students prefer the current type of sandwich, break and return the count of the other type
18            if (count[sandwich] == 0) {
19                return count[1 - sandwich]; // use 1 - sandwich to get the opposite type
20            }
21            // Otherwise, decrement the count of students who prefer the current type of sandwich
22            count[sandwich]--;
23        }
24      
25        // If all sandwiches can be taken by students, return 0
26        return 0;
27    }
28};
29

Typescript Solution

1// Function to count the number of students who will not be able to get their preferred sandwich
2function countStudents(students: number[], sandwiches: number[]): number {
3    // Initialize a count array where index 0 represents students who prefer sandwich type 0,
4    // and index 1 represents students who prefer sandwich type 1.
5    const sandwichPreferenceCount = [0, 0];
6
7    // Count the number of students who prefer each type of sandwich
8    for (const studentPreference of students) {
9        sandwichPreferenceCount[studentPreference]++;
10    }
11
12    // Iterate over the sandwich types in the order they are to be served
13    for (const sandwichType of sandwiches) {
14        // If there are no students left preferring the current sandwich type,
15        // return the count of students preferring the other type
16        if (sandwichPreferenceCount[sandwichType] === 0) {
17            return sandwichPreferenceCount[sandwichType ^ 1];
18        }
19        // Otherwise, decrement the count of students who prefer the current sandwich type
20        sandwichPreferenceCount[sandwichType]--;
21    }
22
23    // If all students can be served with their preferred sandwich type,
24    // return 0 indicating no students are left unserved
25    return 0;
26}
27
Fast Track Your Learning with Our Quick Skills Quiz:

Breadth first search can be used to find the shortest path between two nodes in a directed graph.

Time and Space Complexity

The given Python code snippet implements a function to count the number of students who won't be able to eat their preferred type of sandwich. The time and space complexities of the code are as follows:

  • Time complexity:

The function iterates over each sandwich in the sandwiches list, which has a time complexity of O(n), where n is the number of sandwiches (or students, since they are the same). The operation cnt[v] and the decrement operation cnt[v] -= 1 both operate in constant time, O(1). Therefore, the overall time complexity of the function is O(n).

  • Space complexity:

The space complexity is primarily defined by the space required to store the counter for the students. The Counter object stores an entry for each unique value in the students list, which, in this case, has only two possible values (0 and 1). Therefore, the Counter object space is constant, O(1). Hence, the space complexity of the function is O(1).

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


Recommended Readings


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫