3679. Minimum Discards to Balance Inventory
Problem Description
You are given two integers w and m, and an integer array arrivals, where arrivals[i] represents the type of item arriving on day i (days are 1-indexed).
Items follow these management rules:
- When an item arrives, you must decide immediately whether to keep it or discard it. An item can only be discarded on the exact day it arrives — you cannot remove it later.
- For every day
i, look at the sliding window of the lastwdays:[max(1, i - w + 1), i].- Within any such window, each item type can appear at most
mtimes among the kept items. - If keeping the item arriving on day
iwould push its type's count in the window abovem, then that item must be discarded.
- Within any such window, each item type can appear at most
Your task is to return the minimum number of arrivals that need to be discarded so that every w-day window contains at most m kept items of each type.
In other words, as items arrive one by one, you maintain a sliding window of size w. Whenever a newly arriving item would cause its type to exceed the limit m within the current window, you have no choice but to throw it away. You need to count how many items end up being discarded.
Key points to understand:
- The decision is made online: each item is processed in order, and once kept, it stays in the window until it slides out naturally after
wdays. - Discarded items do not count toward the window's type counts — only kept items matter.
- The greedy strategy of keeping an item whenever possible is optimal: keeping an earlier item never hurts, because it leaves the window sooner than any later item of the same type would, freeing up capacity earlier.
Example walkthrough:
Suppose arrivals = [1, 2, 1, 1], w = 3, m = 1:
- Day 1: type
1arrives. Window[1,1]has zero kept items of type1, so keep it. - Day 2: type
2arrives. Window[1,2]has no type2, so keep it. - Day 3: type
1arrives. Window[1,3]already contains one kept type1(from day 1), so this item must be discarded. - Day 4: type
1arrives. Window[2,4]— the item from day 1 has slid out, and the day-3 item was discarded, so type1count is0. Keep it.
Total discarded: 1.
The solution simulates this process with a counter cnt tracking kept items per type in the current window, and a marked array recording which items were kept, so counts can be correctly decremented when items slide out of the window.
How We Pick the Algorithm
Why Sliding Window?
This problem maps to Sliding Window through a short path in the full flowchart.
A fixed-size sliding window tracks kept item counts per type; each arrival is either kept or discarded greedily depending on whether the window already holds the per-type limit.
Open in FlowchartIntuition
The first thing to notice is that the problem statement itself almost forces our hand: when an arriving item would make its type exceed m in the current window, it must be discarded — there's no decision to make in that moment. So the only real question is: should we ever voluntarily discard an item that could legally be kept, hoping it saves more items later?
The answer is no, and here's the key insight why.
Consider two items of the same type — one arriving earlier, one arriving later. The earlier item entered the window sooner, which means it will also leave the window sooner. If we're choosing which occurrences of a type to keep, an earlier item "occupies" a slot for less remaining time than a later one would. So keeping items as early as possible frees up capacity at the earliest possible moment, which can never be worse for future arrivals.
Put differently: suppose an optimal solution discards some item A that could have been kept, and later keeps an item B of the same type. We can always swap the choice — keep A and discard B instead — without ever violating the window constraint, because A exits every window no later than B does. The total number of discards stays the same. This exchange argument tells us the greedy strategy — keep every item unless the rules force a discard — is optimal.
Once we trust the greedy approach, the implementation becomes a straightforward simulation with a sliding window:
- We need to know, for each arriving item, how many kept items of the same type currently sit in the window
[i - w + 1, i]. A hash mapcnttracks this. - As the window slides forward, the item from day
i - wfalls out. But we must be careful: only items that were actually kept should be subtracted from the counts. Discarded items never contributed tocntin the first place. This is exactly why we maintain themarkedarray —marked[j] = 1means the item on dayjwas kept, so when dayjexits the window we decrementcnt[arrivals[j]]bymarked[j](subtracting0for discarded items,1for kept ones). - For the current item
x: ifcnt[x] >= m, keeping it would exceed the limit, so we discard it and increment the answer. Otherwise, we keep it, setmarked[i] = 1, and bumpcnt[x].
Each item is processed exactly once with O(1) work, giving a clean O(n) time solution with O(n) space for the marked array.
Pattern Learn more about Sliding Window patterns.
Solution Approach
if i >= w: cnt[arrivals[i - w]] -= marked[i - w]
The subtlety here is the multiplication-by-`marked` trick: if the item on day `i - w` was **discarded**, `marked[i - w]` is `0` and the count stays unchanged — exactly right, since discarded items never counted in the first place. If it was **kept**, we subtract `1`. **Step 2: Check the constraint for the current item.** If the window already contains `m` kept items of type `x`, keeping one more would exceed the limit, so the item must be discarded: ```python if cnt[x] >= m: ans += 1
Note that we do not set marked[i], and cnt[x] is not incremented — a discarded item leaves no trace in the window state.
Step 3: Otherwise, keep the item.
else: marked[i] = 1 cnt[x] += 1
We record that the item was kept and increment its type count, so future days within the next w - 1 days will see it in their windows.
Step 4: Return the result.
After processing all arrivals, ans holds the minimum number of discards, which is guaranteed minimal by the greedy exchange argument established earlier.
Trace Example
With arrivals = [1, 2, 1, 1], w = 3, m = 1:
i | x | Slide out | cnt[x] before | Action | cnt after | ans |
|---|---|---|---|---|---|---|
| 0 | 1 | — | 0 | keep | {1: 1} | 0 |
| 1 | 2 | — | 0 | keep | {1: 1, 2: 1} | 0 |
| 2 | 1 | — | 1 ≥ m | discard | {1: 1, 2: 1} | 1 |
| 3 | 1 | day 0 (kept) → cnt[1] -= 1 | 0 | keep | {1: 1, 2: 1} | 1 |
The answer is 1.
Complete Code
class Solution:
def minArrivalsToDiscard(self, arrivals: List[int], w: int, m: int) -> int:
cnt = Counter()
n = len(arrivals)
marked = [0] * n
ans = 0
for i, x in enumerate(arrivals):
if i >= w:
cnt[arrivals[i - w]] -= marked[i - w]
if cnt[x] >= m:
ans += 1
else:
marked[i] = 1
cnt[x] += 1
return ans
Example Walkthrough
Let's trace the algorithm on arrivals = [1, 1, 1, 1, 1, 1], w = 3, m = 2 — six items of the same type, a window of size 3, and at most 2 kept items per type allowed in any window. This example is small but exercises every branch of the code, including the subtle marked trick.
We maintain:
cnt— the number of kept items of each type in the current window,marked[i]—1if the item on dayiwas kept,0otherwise,ans— the number of discards.
i | x | Slides out (i - w) | Effect on cnt[1] | cnt[1] before check | Decision | marked[i] | cnt[1] after | ans |
|---|---|---|---|---|---|---|---|---|
| 0 | 1 | — (window not full) | — | 0 | 0 < 2 → keep | 1 | 1 | 0 |
| 1 | 1 | — | — | 1 | 1 < 2 → keep | 1 | 2 | 0 |
| 2 | 1 | — | — | 2 | 2 ≥ 2 → discard | 0 | 2 | 1 |
| 3 | 1 | index 0, marked[0] = 1 | 2 - 1 = 1 | 1 | 1 < 2 → keep | 1 | 2 | 1 |
| 4 | 1 | index 1, marked[1] = 1 | 2 - 1 = 1 | 1 | 1 < 2 → keep | 1 | 2 | 1 |
| 5 | 1 | index 2, marked[2] = 0 | 2 - 0 = 2 (unchanged) | 2 | 2 ≥ 2 → discard | 0 | 2 | 2 |
Step-by-step commentary:
- Days 1–2 (
i = 0, 1): The window has fewer thanm = 2kept items of type1, so both are kept greedily. Remember the exchange argument — keeping items as early as possible is never worse, since earlier items exit the window sooner. - Day 3 (
i = 2): The window[1, 3]already holds2kept items of type1. Keeping this one would exceedm, so it is forced out. Crucially, it leaves no trace:marked[2]stays0andcnt[1]is not incremented. - Days 4–5 (
i = 3, 4): The window slides. The items from days 1 and 2 were kept (marked = 1), so each timecnt[1]correctly drops by1, freeing a slot. Both new arrivals fit and are kept. - Day 6 (
i = 5): Here is where thecnt[arrivals[i-w]] -= marked[i-w]trick pays off. The item sliding out is from day 3 — but it was discarded, somarked[2] = 0and the subtraction does nothing. The window[4, 6]still genuinely contains2kept items (days 4 and 5), so the new arrival must also be discarded. Without themarkedarray, we would have wrongly decrementedcnt[1]to1and kept this item, violating the constraint.
Final answer: 2 — the items arriving on days 3 and 6 are discarded.
Sanity check against the rules: the kept items land on days 1, 2, 4, 5. Every window of 3 consecutive days — [1,3], [2,4], [3,5], [4,6] — contains at most 2 kept items of type 1, and the greedy exchange argument guarantees no strategy can do better than 2 discards.
Solution Implementation
1from collections import Counter
2from typing import List
3
4
5class Solution:
6 def minArrivalsToDiscard(self, arrivals: List[int], w: int, m: int) -> int:
7 # Counter of values currently kept inside the sliding window of size w
8 window_count = Counter()
9 n = len(arrivals)
10 # kept[i] = 1 if arrivals[i] was kept (not discarded), else 0
11 kept = [0] * n
12 # Total number of discarded arrivals
13 discarded = 0
14
15 for i, value in enumerate(arrivals):
16 # Slide the window: remove the element leaving the window,
17 # but only if it was actually kept earlier
18 if i >= w:
19 window_count[arrivals[i - w]] -= kept[i - w]
20
21 if window_count[value] >= m:
22 # Keeping this arrival would exceed the limit m
23 # within the current window, so discard it
24 discarded += 1
25 else:
26 # Keep this arrival and record it in the window counter
27 kept[i] = 1
28 window_count[value] += 1
29
30 return discarded
311class Solution {
2 public int minArrivalsToDiscard(int[] arrivals, int w, int m) {
3 // Map to track the count of each value among the KEPT items
4 // within the current sliding window of size w
5 Map<Integer, Integer> countMap = new HashMap<>();
6 int n = arrivals.length;
7 // kept[i] = 1 if arrivals[i] was kept, 0 if it was discarded
8 int[] kept = new int[n];
9 // Total number of discarded arrivals
10 int discardCount = 0;
11
12 for (int i = 0; i < n; i++) {
13 int current = arrivals[i];
14
15 // If the window is full, remove the contribution of the element
16 // that slides out of the window (only if it was kept)
17 if (i >= w) {
18 int outgoing = arrivals[i - w];
19 countMap.merge(outgoing, -kept[i - w], Integer::sum);
20 }
21
22 // If the current value already appears m times in the window,
23 // it must be discarded
24 if (countMap.getOrDefault(current, 0) >= m) {
25 discardCount++;
26 } else {
27 // Otherwise, keep it and update its count in the window
28 kept[i] = 1;
29 countMap.merge(current, 1, Integer::sum);
30 }
31 }
32
33 return discardCount;
34 }
35}
361class Solution {
2public:
3 int minArrivalsToDiscard(vector<int>& arrivals, int w, int m) {
4 // Count of each value currently kept within the sliding window
5 unordered_map<int, int> keptCount;
6 int n = arrivals.size();
7
8 // kept[i] = 1 if arrivals[i] is kept (not discarded), 0 otherwise
9 vector<int> kept(n, 0);
10
11 // Total number of discarded arrivals
12 int discarded = 0;
13
14 for (int i = 0; i < n; i++) {
15 int value = arrivals[i];
16
17 // The element leaving the window is arrivals[i - w].
18 // Remove its contribution only if it was kept.
19 if (i >= w) {
20 keptCount[arrivals[i - w]] -= kept[i - w];
21 }
22
23 if (keptCount[value] >= m) {
24 // Keeping this arrival would exceed the limit m
25 // within the current window, so discard it.
26 discarded++;
27 } else {
28 // Keep this arrival and update its count.
29 kept[i] = 1;
30 keptCount[value] += 1;
31 }
32 }
33
34 return discarded;
35 }
36};
371/**
2 * Counts the minimum number of arrivals that must be discarded so that,
3 * within any sliding window of size `w`, each distinct value appears
4 * at most `m` times (kept arrivals only).
5 *
6 * Greedy strategy: scan from left to right; keep an arrival whenever the
7 * current window still has capacity for its value, otherwise discard it.
8 *
9 * @param arrivals - The sequence of arriving item values.
10 * @param w - The size of the sliding window.
11 * @param m - The maximum allowed occurrences of each value per window.
12 * @returns The number of discarded arrivals.
13 */
14function minArrivalsToDiscard(arrivals: number[], w: number, m: number): number {
15 // Frequency of each value among the KEPT arrivals inside the current window
16 const keptCount = new Map<number, number>();
17 const n = arrivals.length;
18
19 // kept[i] = 1 if arrivals[i] was kept, 0 if it was discarded
20 const kept: number[] = new Array<number>(n).fill(0);
21
22 // Total number of discarded arrivals
23 let discarded = 0;
24
25 for (let i = 0; i < n; i++) {
26 const value = arrivals[i];
27
28 // Slide the window: remove the element leaving the window (index i - w).
29 // Only subtract if that element was actually kept.
30 if (i >= w) {
31 const expired = arrivals[i - w];
32 keptCount.set(expired, (keptCount.get(expired) || 0) - kept[i - w]);
33 }
34
35 if ((keptCount.get(value) || 0) >= m) {
36 // The window already holds `m` kept copies of this value: discard it.
37 discarded++;
38 } else {
39 // There is still capacity: keep this arrival and update its count.
40 kept[i] = 1;
41 keptCount.set(value, (keptCount.get(value) || 0) + 1);
42 }
43 }
44
45 return discarded;
46}
47Time and Space Complexity
-
Time complexity:
O(n), wherenis the length of the arrayarrivals. The code iterates through the array exactly once, and each operation inside the loop — updating the countercnt, checkingcnt[x] >= m, and markingmarked[i]— takesO(1)time on average (hash table operations). -
Space complexity:
O(n), wherenis the length of the arrayarrivals. Themarkedarray requiresO(n)space, and the countercntstores at mostO(min(n, w))distinct keys within the sliding window, which is bounded byO(n).
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Decrementing the count for discarded items when the window slides
The most frequent mistake is to unconditionally subtract 1 when an element leaves the window:
# WRONG if i >= w: window_count[arrivals[i - w]] -= 1
This corrupts the window state. A discarded item was never added to window_count, so subtracting 1 when it exits the window makes the count for that type artificially negative. Later arrivals of the same type then pass the cnt[x] >= m check when they should fail, causing kept items to exceed the per-type limit and producing an answer that is too small.
Concrete failure: arrivals = [1, 1, 1, 1], w = 2, m = 1.
- Day 1: keep
1→cnt[1] = 1 - Day 2:
cnt[1] = 1 ≥ m→ discard (correct so far) - Day 3: window slides, wrong code does
cnt[1] -= 1for the kept day-1 item — fine here,cnt[1] = 0, keep it. - Day 4: window slides, wrong code does
cnt[1] -= 1for the discarded day-2 item →cnt[1] = 0instead of1. The day-4 item is kept, but now days 3 and 4 both hold type1inside the same window of size 2 — a constraint violation. The wrong answer is1; the correct answer is2.
Fix: track which items were actually kept, and only decrement for those:
# CORRECT if i >= w: window_count[arrivals[i - w]] -= kept[i - w] # subtracts 0 for discarded items
Pitfall 2: Off-by-one errors in the window boundary
The window covering day i (0-indexed) is [i - w + 1, i], so the element that exits when processing index i is arrivals[i - w]. Writing i > w instead of i >= w, or removing arrivals[i - w + 1], shifts the window by one day. This either keeps an expired item in the count one day too long (over-discarding) or evicts it one day too early (under-discarding, violating the constraint). Always verify with the smallest case: when w = 1, processing index i = 1 must evict index 0.
Pitfall 3: Counting discarded items toward the window
Symmetric to Pitfall 1 — incrementing cnt[x] (or setting kept[i] = 1) even on the discard branch:
# WRONG if window_count[value] >= m: discarded += 1 window_count[value] += 1 # discarded items must leave no trace!
Discarded items do not occupy capacity. Inflating the count makes future same-type arrivals get discarded unnecessarily, yielding an answer that is too large.
Pitfall 4: Using > m instead of >= m in the check
The condition asks whether keeping one more item would push the count above m. If the window already holds m items of type x, adding another gives m + 1 > m, so the test must be cnt[x] >= m. Writing cnt[x] > m allows m + 1 kept items per window.
Pitfall 5: Trying a "smarter" non-greedy strategy
Some attempt to discard an earlier kept item retroactively, or to skip keeping an item "to save room" for future arrivals. Both violate the problem rules (decisions are immediate and irrevocable) and are also unnecessary: keeping an item as early as possible is provably optimal, since an earlier item exits the window no later than any subsequent item of the same type, so it never blocks more future arrivals than the alternative. The straightforward greedy simulation is both required and optimal.
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapWhich algorithm is best for finding the shortest distance between two points in an unweighted graph?
Recommended Readings
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
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
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Want a Structured Path to Master System Design Too? Don’t Miss This!