Sneak Peak BCTCI - Sliding Windows & Binary Search
Sneak Peak BCTCI - Sliding Windows & Binary Search
CRACKING
the
CODING INTERVIEW
Pass Tough CODING Interviews,
get noticed, and Negotiate succesfully
SNEAK PEEK
amazon.com/dp/195570600X
BEYOND
CRACKING
the
CODING INTERVIEW
Pass Tough CODING Interviews,
get noticed, and Negotiate succesfully
0
INTERVIEW CHECKLIST
B E YO N D CR ACK I N G T H E CO D I N G I N T E R V I E W
1
STUDY PLAN
BOOSTERS
B E YO N D CR ACK I N G T H E CO D I N G I N T E R V I E W
Cracking the Coding Interview
189 Programming Questions and Solutions
GAYLE L. MCDOWELL
MIKE MROCZK A
ALINE LERNER
NIL MAMANO
CareerCup, LLC
Palo Alto, CA
BEYOND CRACKING THE CODING INTERVIEW
All rights reserved. No part of this book may be reproduced in any form by any electronic or me-
chanical means, including information storage and retrieval systems, without permission in writing
from the author or publisher, except by a reviewer who may quote brief passages in a review.
Published by CareerCup, LLC, Palo Alto, CA. Compiled Feb 15, 2025.
To my dog, my wife, and our readers (and not necessarily in that order)—
Mike
To my two wonderful kids (or if I have more, then whichever two are the most wonderful)—
Aline
You can access all of our online materials and Talk with the authors, get help if you're stuck, and
bonus chapters here: geek out with us on Discord
Figure 1. Landscape of Interview Questions, for someone who prepares with BCtCI
2 Teach you problem-solving strategies so you can figure out 80% of questions on your own, even if you
haven't seen the idea before This is what the problem-solving boosters (Chapter 24: Problem-Solving
Boosters, pg 249) are for The remaining 20% of questions rely on tricks (ideas that are really hard to
come up with on your own if you haven't seen them before) 1
Combining these two, after going through the book, you should be able to tackle all but 20% * 20% = 4%
of questions, which are those based on niche topics and requiring tricks But if that happens, you've been
truly unlucky
1 Our mantra? If you encounter something once, it’s a trick; if you encounter it repeatedly, it’s a tool
I
CHAPTER 29
BINARY SEARCH
AI interviewer, replays, and more materials for this chapter at bctci co/binary-search
▶ Prerequisites: None
When it comes to binary search, software engineers are split in half: One camp thinks it's too basic to be an
interview question, and the other dreads it because they always mess up the index manipulation
The first group overlooks the fact that binary search has many uses beyond the basic "find a value in a sorted
array " Far from it, binary search has many non-obvious applications, which we'll cover in this chapter For
the second group, we'll provide a recipe focusing on simplicity and reusability across applications—even
the unconventional ones we just foreshadowed
Check the solution in the footnote 1 Regardless of what you found, the point is that it is easy to miss errors
in a binary search implementation
Here is one way to do it correctly:
We'll punt on breaking down this solution until we talk about our transition-point recipe (pg 330)
1 We were not completely honest—there isn't just one bug; there are closer to six, depending on how you count them
(1) r is initialized out of bounds, (2 & 3) we check mid instead of arr[mid] (twice!), (4) we update r when we should
be updating l, (5) l should be set to mid+1, not mid-1, and (6) r should be set to mid-1, not mid+1
2 https://www thetimes com/article/i-have-owned-11-bikes-this-is-how-they-were-stolen-d3r553gx3
328 B E YO N D CR ACK I N G T H E CO D I N G I N T E R V I E W ▸ C ATA LO G O F T E CH N I C A L TO PI C S
Figure 2.
Figure 3.
Initially, we don't know where the transition point is, but we can binary search for it:
Figure 4.
1 def is_before(val):
2 return not is_stolen(val)
3
4 def find_bike(t1, t2):
5 l, r = t1, t2
6 while r - l > 1:
7 mid = (l + r) // 2
8 if is_before(mid):
9 l = mid
10 else:
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 29 ▸ B I N A RY S E A R CH 329
11 r = mid
12 return r
At the beginning, l is in the 'before' region, and r is in the 'after' region From there, l never leaves the
'before' region and r never leaves the 'after' region, but they end up next to each other: at the end, l is the
last 'before' and r is the first 'after '
Here is the kicker: every binary search solution can be reframed as finding a transition point. For
instance, Problem 29 1: Search In Sorted Array can be reframed as finding the "transition point" from elements
smaller than target to elements greater than or equal to target
If we learn a recipe for finding transition points, we'll be able to use it for every binary search problem We
don't need specialized recipes for various problem types
TRANSITION-POINT RECIPE
In an important interview—with tensions mounting and anxiety running high—you are not working at total
capacity We joke that you are ~20% dumber than during practice To counter this, it helps to have a recipe
you know well for tricky algorithms like binary search A good recipe should be easy to remember, have
straightforward edge cases, and make it easy to avoid off-by-one errors
The point of the initialization and the initial edge cases is to get to a setting that looks like the first row
of Figure 4: l must be in the 'before' region, and r must be in the 'after' region The three edge cases are
designed to ensure this
Once we get to that point, the main while loop is the same for every problem—no tweaking needed!
The loop has the following invariants, which are guarantees that make our lives easier:
• From start to end, l is in the 'before' region, and r is in the 'after' region They are never equal and never
cross over 3
• The midpoint is always strictly between l and r (l < mid < r), which guarantees we always make
progress (we don't need to worry about infinite loops)
• When we exit the loop, l and r are always next to each other
3 If we were a little more willing to buck conventions, we'd rename from l and r to b and a, since they always map to
'before' and 'after' values However, you might get odd looks from an interviewer if you do this!
330 B E YO N D CR ACK I N G T H E CO D I N G I N T E R V I E W ▸ C ATA LO G O F T E CH N I C A L TO PI C S
Something that is typically tricky with binary search is the exit condition of the loop Here, we keep going
until l and r are next to each other (i e , until the 'unknown' region in Figure 4 is empty), which happens
when r - l is 1 That's why the condition says r - l > 1 4
Another tricky part is knowing what to return With this recipe, we just need to reason about the transition
point: do we need the final 'before' or the first 'after'?
We recommend starting by defining the is_before() function Keep in mind that, for binary search to work,
we must define it in such a way that the search range is monotonic: all the 'before' elements must appear
before all the 'after' elements That's why binary search doesn't work on unsorted arrays
The while loop is just like the recipe, except that we didn't factor out is_before() into a helper function:
1 while r - l > 1:
2 mid = (l + r) // 2
3 if arr[mid] < target:
4 l = mid
5 else:
6 r = mid
Finally, when we find the transition point, we consider what that means: l is at the largest value smaller than
the target, and r is at the smallest value greater than or equal to the target So, if the target is in the array
at all, it must be at index r
1 if arr[r] == target:
2 return r
3 return -1
What to do at the end depends on how we define the 'before' region We could have also defined 'before'
as "less than or equal to the target," in which case, at the end, we would have to check the element at l
instead of r
This recipe is a bit like a one-size-fits-all pair of socks While more concise (but less reusable) implementations
may exist for some problems, there is value in needing only one easy-to-remember recipe
4 We could have also written this in other ways, like r > l + 1 One way to remember the formula for the number of
elements between l and r, r-l-1, is that it looks like a sleepy cat
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 29 ▸ B I N A RY S E A R CH 331
4 First 'p':
Find the first word that begins with 'p' in an array of words in dictionary order, if any
["apple", "banana", "peach", "strawberry"]
Including 0 in the 'before' region would be a mistake: if there are multiple zeros, l would point to the last
one, but the goal is to return the first one
First 'p': The 'before' region consists of words that start with a–o and the 'after' region consists of words that
start with p–z If there are words that start with 'p', the first one will be at index r
Including words that start with 'p' in the 'before' region would be incorrect: if we inserted another word
starting with 'p,' like "pear," l would point to the last word starting with 'p' rather than the first one
Post-processing requires a bit of thought If the target is in the array, it will be at l We can peek at arr[l]
and return l if it is the target Otherwise, we need to find the closest value to it, which could be at l or r We
return either l or r, based on whether arr[l] or arr[r] is closer
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 29 ▸ B I N A RY S E A R CH 333
• Writing indices on the top of the array makes it faster to do midpoint calculations
• You can also draw the transition point between 'before' and 'after '
Instead of Try
[1, 2, 2, 3, 3, 4, 5, 8, 8] 0 1 2 3 4 5 6 7 8
^ ^ ^ [1 2 2 3 3 4|5 8 8]
left mid right l
r
m
m = (0+8)/2
So, which inputs should you validate and visualize? Consider the following edge cases, when applicable:
• The range is empty
• The range only has 'before' elements
• The range only has 'after' elements
• The target is not in the array
• The target is in the array multiple times
ANALYSIS
But officer, it is O(log n)!
In the Big O analysis chapter, we defined log2(n) as roughly the number of times we need to halve a number
to reach 1 Binary search halves the search range at each step, so binary search converges in O(log n)
iterations, where n is the size of the range (e g , t2 - t1 in Problem 29 2: CCTV Footage (pg 327) 5
Some binary search implementations stop early when arr[mid] == target For simplicity, our recipe
doesn't have that, which means that it takes O(log n) time even in the best case That's fine—we mainly
care about the worst case
Don't forget to factor in the time it takes to compute is_before() in the runtime calculation if it is not
constant!
Finally, the extra space is O(1) Binary search can also be implemented recursively, in which case the extra
space increases to O(log n) for the call stack
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 29 ▸ B I N A RY S E A R CH 335
12 r = mid
13 return min(arr[l], arr[r])
Interestingly, in the variation of this problem where we allow duplicates in the input, binary search does not
work: if mid lands on a value that is the same as the previous one and the next one, we can't tell if we are in
the descending prefix or the ascending suffix.6
According to the statement, index 0 is 'before' and index n-1 is 'after,' so we don't need to worry about the
initial edge cases. We just need to find the transition point and return r.
6 In fact, for this variant, we cannot do better than O(n) time. The array could consist of all 1's and a single 0, which could
be anywhere and can only be found with a linear scan.
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 29 ▸ B I N A RY S E A R CH 337
Figure 5.
This would be a sorted array with R*C elements We can binary search over this 'flattened-grid' array without
actually creating it We'd start with l = 0 and r = R*C - 1 To define the is_before() function, we must
map the 'flattened-grid array' index to the actual grid coordinates on the fly:
1 def is_before(grid, i, target):
2 num_cols = len(grid[0])
3 row, col = i // num_cols, i % num_cols
4 return grid[row][col] < target
Once we find the transition point, we have to map r back to grid coordinates to check if the target is there
and return them
REUSABLE IDEA: GRID FLATTENING
If we want to iterate or search through a grid with dimensions RxC as if it was a 'normal' array of length
R*C, we can use the following mapping from grid coordinates to "flattened-grid array" coordinates:
[r, c] → r * C + c
and the reverse mapping to go from "flattened-grid array" coordinates to grid coordinates:
i → [i // C, i % C]
For instance, cell [1, 2] in Figure 5 (the 9) becomes index 1 * 4 + 2 = 6, and, conversely, index 6
becomes cell [6 // 4, 6 % 4] = [1, 2]
The total runtime is O(log n), where n is the size of the huge array
338 B E YO N D CR ACK I N G T H E CO D I N G I N T E R V I E W ▸ C ATA LO G O F T E CH N I C A L TO PI C S
GUESS-AND-CHECK TECHNIQUE
Since I was unable to come up with any approach,
I knew the solution was going to be Binary Search
As the stolen bike story illustrates, binary search is often used in problems where it is not an obvious choice
Now that we have a solid recipe for any binary search problem, we will discuss the guess-and-check tech-
nique, which allows us to use binary search on many optimization problems
Recall that an optimization problem is one where you are asked to find some minimum or maximum value,
subject to some constraint For example, consider the following problem:
• For max_sum < max(arr), the answer is "no" (some numbers are too big to be in a subarray, even by
themselves)
• For max_sum == sum(arr), the answer is "yes" (any split will do)
7 You need to choose k out of n-1 possible splitting points, so there are (n-1 choose k) options, which is O((n-1)k)
= O(nk) for any constant value of k If k is n/2, the number becomes exponential on n (O(2n/√n) to be exact, but you
don't need to worry about where that formula comes from) Once k gets larger than n/2, the number of possibilities
starts decreases (if we are picking more than half the points, we can think about picking the points not to split at, of
which there are fewer than n/2)
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 29 ▸ B inary S earch 339
We can binary search for the transition point where the answer goes from "no" to "yes" with our transition
point recipe. The value x corresponding to the first "yes" is the value of the optimal solution.
To implement our is_before(max_sum) function, we need to be able to compute the answer to the ques-
tion. Thankfully, it is much easier than the original problem: we can grow each subarray up until the point
where its sum would exceed max_sum. At that point, we start a new subarray, and so on. If we need more
than k subarrays, the answer is "no." Otherwise, the answer is "yes."
1 # "Is it impossible to split arr into k subarrays, each with sum <= max_sum?"
2 def is_before(arr, k, max_sum):
3 splits_required = get_splits_required(arr, max_sum)
4 return splits_required > k
5
6 # Returns the minimum number of subarrays with a given maximum sum.
7 # Assumes that max_sum >= max(arr).
8 def get_splits_required(arr, max_sum):
9 splits_required = 1
10 current_sum = 0
11 for num in arr:
12 if current_sum + num > max_sum:
13 splits_required += 1
14 current_sum = num # Start a new subarray with the current number.
15 else:
16 current_sum += num
17 return splits_required
18
19 def min_subarray_sum_split(arr, k):
20 l, r = max(arr), sum(arr) # Range for the maximum subarray sum.
21 if not is_before(arr, k, l):
22 return l
23 while r - l > 1:
24 mid = (l + r) // 2
25 if is_before(arr, k, mid):
26 l = mid
27 else:
28 r = mid
29 return r
Let S be the sum of arr. Binary search will take O(log S) steps to converge, and each is_before() check
takes O(n) time. The total runtime is O(n log S). Depending on whether O(k) or O(log S) is larger, DP
or binary search will be better. Neither dominates the other.
To recap, the guess-and-check technique involves narrowing in on the value of the optimal solution by
guessing the midpoint and checking whether it's too high or too low. To start, we need lower and upper
bounds for the value of the optimal solution (if the bounds are not obvious, exponential search can help).
For minimization problems (like Problem 29.9: Min-Subarray-Sum Split), there is often a transition point where
smaller values do not satisfy the constraint, but larger values do. Conversely, for maximization problems,
there is often a transition point where larger values do not satisfy the constraint, but smaller values do.
"Is it easier to solve the yes/no version of the problem, where we just check if a given value (optimal or
not) satisfies the constraint?"
Think of it like making a deal: You get to solve an easier problem (checking if a specific value
satisfies the constraint), but you pay a 'logarithmic tax' in the runtime (to binary searching for the
transition point)
We've seen the guess-and-check technique before, in the Boundary Thinking chapter with Problem 22 1:
Tunnel Depth (pg 232) It is easier to binary search for the first depth where the tunnel doesn't reach than
to try to compute the maximum depth directly
▶ Example: a = 18, b = 5
Output: 3. After pouring 5 gallons three times, the first container will be
at 15, and 5 more gallons would make it overflow.
8 I (Mike) am the interviewer in this particular interview This question was the opener for the Boundary Thinking chapter
and you can see a candidate make the same mistakes we discuss in that chapter and me walking through the boundary
thinking mentality
9 Hypothetically! It's fine to jump around chapters when reading this actual book
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 29 ▸ B I N A RY S E A R CH 341
• If you finish a chapter on a given day, you practice for the rest of the day and don't start the next chapter
until the next day
• len(page_counts) ≤ days
Figure 6. Example input for Problem 9 The empty cells are 0's and the cells with water are 1's
and above the answer, closing in on the number—something we can do with binary search. Despite the
problem not having clear triggers for binary search, it's a natural fit for this thought process.
1 def num_refills(a, b):
2 # "Can we pour 'num_pours' times?"
3 def is_before(num_pours):
4 return num_pours * b <= a
5
6 # Exponential search (repeated doubling until we find an upper bound).
7 k = 1
8 while is_before(k * 2):
9 k *= 2
10
11 # Binary search between k and k*2
12 l, r = k, k * 2
13 while r-l > 1:
14 gap = r - l
15 half_gap = gap >> 1 # Bit shift instead of division
16 mid = l + half_gap
17 if is_before(mid):
18 l = mid
19 else:
20 r = mid
21 return l
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 29 ▸ B I N A RY S E A R CH 343
Checking the number of ones in a row takes O(log n) time Checking the number of ones in an entire grid
takes O(n log n) time The total time is O(n log n log k), where k is the number of pictures
CONCLUSIONS
Binary Search triggers: The input is a sorted array/string The brute force involves repeated linear
scans We are given an optimization problem that's hard to optimize directly
Keywords: sorted, threshold, range, boundary, find, search, minimum/maximum, first/last, small-
est/largest
Binary search is often a step or a possible optimization in more complicated algorithms Binary search is so
common that it can (and will) be seen alongside almost every other Catalog topic, like Graphs (Problem 36 9:
First Time All Connected, pg 468), Sliding Windows (Chapter 38: Longest Repeated Substring, pg 523),
and Greedy Algorithms (Problem 41 6: Time Traveler Max Year, pg 593)
The key idea in this chapter is that we can reframe every binary search problem as finding a transition point
This way, we only need one recipe for every scenario—the transition-point recipe (pg 329)—and we can
focus our energy on more complicated parts of the code
At this point, you should be ready to start adding binary search problems to your practice rotation You can
find the problems in this chapter and additional problems in the companion AI interviewer
34 4 B E YO N D CR ACK I N G T H E CO D I N G I N T E R V I E W ▸ C ATA LO G O F T E CH N I C A L TO PI C S
ONLINE RESOURCES
Online resources for this chapter include:
• A chance to try each problem in this chapter in AI Interviewer
• Interview replays that show specific mistakes people make with binary search
problems
• Full code solutions for every problem in the chapter in multiple programming
languages
Try online at bctci co/binary-search
View online materials for Beyond Cracking the Coding Interview at bctci.co
I
CHAPTER 38
SLIDING WINDOWS
AI interviewer, replays, and more materials for this chapter at bctci co/sliding-windows
▶ Prerequisites: None
In this chapter, we will use the sliding window technique to tackle problems about finding or counting
subarrays 1
We will use the following setting for problems throughout this chapter: a bookstore is looking at the number
of book sales The sales for each day are stored in an array of non-negative integers called sales We say
a good day is a day with at least 10 sales, while a bad day is a day with fewer than 10 sales An interviewer
could ask questions such as the following:
• Find the most sales in any 7-day period (Problem 1)
• Find the most consecutive days with no bad days (Problem 5)
• Find the longest period of time with at most 3 bad days (Problem 8)
• Find the shortest period of time with more than 20 sales, if any (Problem 14)
• Count the number of subarrays of sales with at most 10 bad days (Problem 18)
• Count the number of subarrays of sales with exactly 10 bad days (Problem 19)
• Count the number of subarrays of sales with at least 10 bad days (Problem 20)
All these questions receive an array as input, sales The first four ask us to find a subarray, while the last three
ask us to count subarrays, making them ideal candidates for the sliding window technique In this chapter,
we will cover variants of the sliding window technique to tackle each of the mentioned problems and more
The basic idea of a sliding window is to consider a subarray (the "window"), marked by left (l) and right (r)
pointers We move or "slide" the window to the right by increasing the l and r pointers, all while computing
some value about the current window 2 3
1 Beyond DS&A, the term 'sliding window' is also used in network protocols like TCP (https://en wikipedia org/wiki/
Transmission_Control_Protocol) and in machine learning architectures like convolutional neural networks (https://
en wikipedia org/wiki/Convolutional_neural_network)
2 A sliding window is a special case of the two-pointer technique Like in the Two Pointers chapter, we use the terms
"pointer" and "index" interchangeably
3 Sliding windows are usually not useful for problems about subsequences because they don't have a good way of
dealing with "skipping" elements Subsequence problems are more commonly tackled with other techniques that
we will see later, like dynamic programming or backtracking
510 B E YO N D CR ACK I N G T H E CO D I N G I N T E R V I E W ▸ C ATA LO G O F T E CH N I C A L TO PI C S
• There is usually an objective that makes some subarrays "better" than others For example:
• Less commonly, if there is no objective, the goal may be to count the number of valid subarrays
For instance, in the first bookstore problem, the constraint is "the length of the subarray must be 7," and
the objective is to maximize the sum In the last one, the constraint is "at least 10 bad days," and there is
no objective since it is a counting problem Can you identify the constraints and objectives for the other
bookstore problems?
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 38 ▸ SLIDING WINDOWS 511
As long as each iteration grows or shrinks the window (or both), a sliding window algorithm takes
O(n*T) time, where n is the size of the array we are sliding over and T is the time per iteration
Typically, we will be unlikely to reduce the number of iterations—we must reach the end of the array—so we
should focus on reducing T: the time per iteration We should try to get it down to constant time
In terms of space analysis, remember that the window is not materialized, it is just identified by the two
pointers So, the space analysis will depend on what other information about the window we need to store
FIXED-LENGTH WINDOWS
In fixed-length window problems, we have to find a subarray under the constraint that it has a given length
Such problems, which are fairly common, are on the easier side because there are not many subarrays to
consider: for a value k in the range 1 ≤ k ≤ n, an array only has n-k+1 = O(n) subarrays of length k—a
lot fewer than O(n2) Recall the first opening problem:
4 Our convention is that l is inclusive and r is exclusive, but this is merely our convention If you prefer to consider r as
inclusive, this is equally correct, but be sure to update the little details like the size of the window (which would now
be r - l + 1) Whatever you do, be explicit about your convention and make sure the little details match
512 B E YO N D CR ACK I N G T H E CO D I N G I N T E R V I E W ▸ C ATA LO G O F T E CH N I C A L TO PI C S
Output: 44. The 7-day period with the most sales is [5, 0, 1, 0, 15, 12, 11]
▶ Example: sales = [0, 3, 7, 12]
Output: 0. There is no 7-day period.
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 38 ▸ SLIDING WINDOWS 513
If the window has a length of 7, we end the iteration by shrinking it so that when we grow it in the next itera-
tion, it will have the right size again Like growing, shrinking consists of two actions: updating window_sum
and increasing l
The algorithm ends when the window can no longer grow (r == len(sales))
We can put these ideas together in a general recipe for fixed-length window problems:
By "data structures," we mean any information about the window that we need to maintain as we slide it in
order to evaluate each window quickly The data structures that we need change from problem to problem,
and they could range from nothing at all to things like sets and maps In the following problem set, you will
have to consider what information to store about the window and how to update it efficiently
# Maintaining information about the window as it slides is a key idea in designing efficient sliding windows
NESTED LOOPS ARE TOO SLOW FOR SLIDING WINDOW QUESTIONS INTERVIEW REPLAY
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 38 ▸ SLIDING WINDOWS 515
We remove book titles from the map if their count goes back down to 0 This way, the size of the map always
represents the number of unique keys (book titles) in the window, and the window satisfies the constraint
if the map size is k The extra space of our solution is O(k) 5
1 def has_unique_k_days(best_seller, k):
2 l, r = 0, 0
3 window_counts = {}
4 while r < len(best_seller):
5 if not best_seller[r] in window_counts:
6 window_counts[best_seller[r]] = 0
7 window_counts[best_seller[r]] += 1
8 r += 1
9 if r - l == k:
10 if len(window_counts) == k:
11 return True
12 window_counts[best_seller[l]] -= 1
13 if window_counts[best_seller[l]] == 0:
14 del window_counts[best_seller[l]]
15 l += 1
16 return False
RESETTING WINDOWS
We call the next type of sliding window "resetting windows " It is for problems where a bigger window is
usually better, but a single element in the array can make the whole window invalid Our approach will be
simple: grow the window if we can, and otherwise reset it to empty past the problematic element.
Recall the second opening bookstore problem:
5 Don't forget that when storing strings in a map, the space complexity of the map is not just the number of strings, as
we also need to factor in the length of the strings For this problem, we said that all the titles would have length at
most 100, so the space complexity is O(100 * k) = O(k)
516 B E YO N D CR ACK I N G T H E CO D I N G I N T E R V I E W ▸ C ATA LO G O F T E CH N I C A L TO PI C S
1 def max_no_bad_days(sales):
2 l, r = 0, 0
3 cur_max = 0
4 while r < len(sales):
5 can_grow = sales[r] >= 10
6 if can_grow:
7 r += 1
8 cur_max = max(cur_max, r - l)
9 else:
10 l = r+1
11 r = r+1
12 return cur_max
Unlike in the fixed-length window case, a resetting window stays valid throughout the algorithm We also
introduced a can_grow variable to decide whether to grow or reset 6 When sales[r] is a bad day, we reset
the window by moving both l and r past the problematic element
Once the window cannot grow anymore (r == len(sales)), we stop, as we surely won't find a bigger
window by shrinking it
We can put these ideas together in a general recipe for resetting window problems
Now that we have seen two types of sliding windows, it is worth mentioning that problems can fit the criteria
for more than one window type
Recall Problem 38 4: "Given the array best_seller and a number k with 1 ≤ k ≤ len(sales), return
whether there is any k-day period where every day has the same best-selling title " We can solve it with a
fixed-length window like we saw, or with a resetting window:
6 You could skip declaring the variable can_grow and put the condition directly in the if statement, but the name
"can_grow" makes it clear what the if/else cases correspond to, so it is extra easy for the interviewer to follow
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 38 ▸ SLIDING WINDOWS 517
We grow the window when it is (a) empty or (b) the next title is the same as every element in the window
We reset the window when the next element is different from the ones in the window In that case, rather
than skipping over the element that is different; we start growing a new window from that new element
1 def has_enduring_best_seller_streak(best_seller, k):
2 l, r = 0, 0
3 cur_max = 0
4 while r < len(best_seller):
5 can_grow = l == r or best_seller[l] == best_seller[r]
6 if can_grow:
7 r += 1
8 if r - l == k:
9 return True
10 else:
11 l = r
12 return False
1 def max_subarray_sum(arr):
2 max_val = max(arr)
3 if max_val <= 0: # Edge case without positive values.
4 return max_val
5 l, r = 0, 0
6 window_sum = 0
7 cur_max = 0
8 while r < len(arr):
9 can_grow = window_sum + arr[r] >= 0
10 if can_grow:
11 window_sum += arr[r]
12 r += 1
13 cur_max = max(cur_max, window_sum)
14 else:
15 window_sum = 0
16 l = r+1
17 r = r+1
18 return cur_max
MAXIMUM WINDOWS
We are going to tackle general maximization problems (maximum length, maximum sum, etc ) with what
we call maximum windows Maximum windows grow when they can and shrink when they must. They are
similar to resetting windows, but when we encounter an element that makes the window invalid, we don't
discard the whole window and reset it Instead, we shrink it element by element (by increasing l) until it
becomes valid again
7 In both cases, we need to increment r You can see in our implementation that we could factor out that increment
outside of the if/else cases and save a line of code However, as we mentioned on page 297, when trying to come
up with a valid solution, it is easier to think about each case independently first—shared code between the cases can
make it harder to reason about the correctness of your code If you have time, once you are sure your code is correct,
you can do a 'clean-up' pass to tidy up the code
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 38 ▸ SLIDING WINDOWS 519
Many elements of the solution should look similar to the resetting window recipe, like the can_grow variable
The only new part is what happens when we cannot grow the window We remove only the first element in
the window (sales[l])
Here is a recipe for maximum windows Note how, before shrinking the window, we need to check the case
where the window is empty (l == r)—an empty window cannot be shrunk! For many problems, an empty
window is always valid, so we can omit this check (for example, in this problem, an empty window is always
valid because it has 0 bad days)
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 38 ▸ SLIDING WINDOWS 521
# When a problem asks you to choose k elements to change (or flip, remove, etc ), the problem can often
be reframed in terms of finding a window with at most k elements that need to be changed
8 If k is a constant, the number of subsets of size k, denoted (n choose k), is O(nk) The worst case is when k is n/2,
as (n choose n/2) = O(2n/√n) Once k gets larger than n/2, the number of possibilities starts decreasing For
instance, there are only n subsets of size n-1
522 B E YO N D CR ACK I N G T H E CO D I N G I N T E R V I E W ▸ C atalog of T echnical Topics
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 38 ▸ SLIDING WINDOWS 523
Finally, there are also problems that have the maximum window property, meaning that the maximum
window recipe gives the optimal answer, but it is just really hard to implement efficiently Here is an example
of a classic problem:
9 Kadane's algorithm (Solution 38 6: Max Subarray Sum (pg 518) for the maximum subarray sum problem is an excep-
tion
524 B E YO N D CR ACK I N G T H E CO D I N G I N T E R V I E W ▸ C ATA LO G O F T E CH N I C A L TO PI C S
For this particular problem, we can try the guess-and-check technique we learned in the Binary Search
chapter (pg 338) That is, we can try to binary search over the length of the optimal window. This approach
starts by asking: "If I somehow knew the length of the optimal window, would that make the problem
easier?" The answer is often yes because then we can use the fixed-length window recipe, which is the most
straightforward one In this case, the fixed-length window version of the problem is "For a given k, is there a
substring of length k that appears more than once?" If we can solve this efficiently, we can then binary search
for the transition point in the range of values of k where the answer goes from "yes" to "no" The fixed-length
window version can be solved in O(n) time using a rolling hash (bctci co/set-and-map-implementations,
Rolling Hash Algorithm section), leading to O(n log n) total time
# When a problem has the maximum window property, but you cannot find an efficient way to check
if the window is valid or evaluate the window, consider using the guess-and-check technique For an
'extra' factor of O(log n) in the runtime, it turns the problem into a potentially easier fixed-length
window problem
MINIMUM WINDOWS
Minimum window problems are the opposite of maximum window problems We try to find a window as
short as possible, but the constraint restricts how small valid windows can be We'll use 'minimum windows',
which grow when they must and shrink when they can.
Recall the fourth opening bookstore problem:
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 38 ▸ SLIDING WINDOWS 525
1 def shortest_over_20_sales(sales):
2 l, r = 0, 0
3 window_sum = 0
4 cur_min = math.inf
5 while True:
6 must_grow = window_sum <= 20
7 if must_grow:
8 if r == len(sales):
9 break
10 window_sum += sales[r]
11 r += 1
12 else:
13 cur_min = min(cur_min, r - l)
14 window_sum -= sales[l]
15 l += 1
16 if cur_min == math.inf:
17 return -1
18 return cur_min
Unlike the other recipes, we initialize the result (cur_min) to infinity because we update it by taking the
minimum At the end, we need to check if it is still infinity, which means that we didn't find any valid windows
It is a common mistake to forget this final check!
In the main loop, we start each iteration by declaring a variable must_grow (instead of can_grow for maxi-
mum windows) which indicates if the current window is invalid If we must grow, there is one edge case to
consider: if r == len(sales), we ran out of elements to grow, so we break out of the loop We have this
edge case for minimum windows but not maximum windows because the while-loop condition is different:
we don't stop as soon as r gets to the end because it might still be possible to make the window smaller
and get a better answer
If must_grow is false, then we have a valid window, so we update the current minimum first and then shrink
the window to see if we can make it even smaller
We can put these ideas together in a general recipe for minimum window problems
526 B E YO N D CR ACK I N G T H E CO D I N G I N T E R V I E W ▸ C ATA LO G O F T E CH N I C A L TO PI C S
Recall that maximum windows only work for problems that have what we call the maximum window
property? For the minimum window recipe to work, we need the opposite property: Shrinking an invalid
window never makes it valid We call this the minimum window property It means that if the current
window is invalid, we definitely need to grow it
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 38 ▸ SLIDING WINDOWS 527
17 if missing[s1[r]] == 0:
18 distinct_missing -= 1
19 r += 1
20 else:
21 cur_min = min(cur_min, r - l)
22 if s1[l] in missing:
23 missing[s1[l]] += 1
24 if missing[s1[l]] == 1:
25 distinct_missing += 1
26 l += 1
27 return cur_min if cur_min != math.inf else -1
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 38 ▸ SLIDING WINDOWS 529
the answer is the length of the input array) If we can find the smallest window with B - k bad days, then
we can flip every bad day outside the window and maximize the prefix and suffix without bad days
With this reframing, the problem becomes a standard minimum window problem
AT-MOST-K COUNTING
Note that the code is exactly the same as max_at_most_3_bad_days() from the previous section except
for computing count instead of cur_max (and k instead of 3)
# If a problem has the maximum window property, the maximum window recipe also works for At-Most-K
counting problems Whenever we add an element to the window, we add to the running count all the
valid subarrays ending at that element
Fortunately, At-Most-K counting problems are a bit of a 'freebie' because we can reuse the maximum window
recipe In the next section, we'll see a trick that makes Exactly-K counting problems equally easy!
As mentioned, this only works if the problem has the maximum window property For instance, if we allow
negative numbers in the array in this problem, we cannot use the algorithm anymore
EXACTLY-K COUNTING
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 38 ▸ SLIDING WINDOWS 531
5 count_at_most_k_bad_days(sales, k-1)
We re-used the At-Most-K counting function count_at_most_k_bad_days() from the previous section
For k == 0, 'at most 0' and 'exactly 0' are the same For k == 1, the code works because the number of
subarrays with exactly 1 bad day is equal to the number of subarrays with at most 1 bad day (meaning 0
or 1 bad days) minus the number of subarrays with at most 0 bad days This applies to any higher k as well!
For example, Figure 7 shows an example sales array and all 16 subarrays with at most 2 bad days Of those,
10 have at most 1 bad day, meaning there are 16 - 10 = 6 subarrays with exactly 2 bad days By the same
logic, there are 10 - 2 = 8 subarrays with exactly 1 bad day
Figure 7. The set of all subarrays with at most 2 bad days, which contains the set of subarrays with
at most 1 bad day, which contains the set of subarrays with no bad days
Since we need to make two calls to the At-Most-K solution, the runtime is twice as long, which is still O(n)
Exactly-K counting problems can also be solved in linear time with prefix sums: see Problem 38 6: Max
Subarray Sum (pg 517) The prefix-sums approach works even if the array has negative numbers.
AT-LEAST-K COUNTING
The latter half does not identify valid subarrays, so we don't count them In total, we have n +
(n2 - n)/2 = (n+1)*n/2 subarrays
The subarrays with at least k bad days are those with k bad days, those with k+1 bad days, those with k+2
bad days, and so on Therefore, to count the subarrays with at least k bad days, we can start with the count
of all subarrays (of which there are n*(n+1)/2) and subtract the number of subarrays with at most k-1 bad
days Something we already saw how to compute!
1 def count_at_least_k_bad_days(sales, k):
2 n = len(sales)
3 total_subarrays = n*(n+1)//2
4 if k == 0:
5 return total_subarrays
6 return total_subarrays - count_at_most_k_bad_days(sales, k-1)
# If the At-Most-K version of a counting problem has the maximum window property, it can be reused to
solve the At-Least-K version
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 38 ▸ SLIDING WINDOWS 533
3 window_drops = 0
4 count = 0
5 while r < len(arr):
6 can_grow = r == 0 or arr[r] >= arr[r-1] or window_drops < k
7 if can_grow:
8 if r > 0 and arr[r] < arr[r-1]:
9 window_drops += 1
10 r += 1
11 count += r - l
12 else:
13 if arr[l] > arr[l+1]:
14 window_drops -= 1
15 l += 1
16 return count
17
18 def count_exactly_k_drops(arr, k):
19 if k == 0:
20 return count_at_most_k_drops(arr, 0)
21 return count_at_most_k_drops(arr, k) - count_at_most_k_drops(arr, k-1)
22
23 def count_at_least_k_drops(arr, k):
24 n = len(arr)
25 total_count = n*(n+1)//2
26 if k == 0:
27 return total_count
28 return total_count - count_at_most_k_drops(arr, k-1)
10 This is the same logic behind how we use prefix sums to answer range sum queries (pg 612).
View online materials for Beyond Cracking the Coding Interview at bctci.co
CH A P T E R 38 ▸ S liding W indows 535
12 if can_grow:
13 if not arr[r] % 3 in window_counts:
14 window_counts[arr[r] % 3] = 0
15 window_counts[arr[r] % 3] += 1
16 r += 1
17 count += r - l
18 else:
19 window_counts[arr[l] % 3] -= 1
20 if window_counts[arr[l] % 3] == 0:
21 del window_counts[arr[l] % 3]
22 l += 1
23 return count
We could have made the code a bit more efficient by replacing the dictionary with an array of length 3, since
the set of keys that we are using is {0, 1, 2}.
KEY TAKEAWAYS
If you want to try using a sliding window, your first goal should be identifying the constraint and the objective.
Based on those, you can choose the most appropriate window type: see the fixed-length window recipe (pg
513), the resetting window recipe (pg 516), the maximum window recipe (pg 520), and the minimum
window recipe (pg 526).
Once you choose a recipe, the next question is:
▶ What information do I need about the window to check its validity and evaluation efficiently?
Based on the answer, we will pick which window data structures to maintain as we slide the window (and
remember, if you can't find appropriate window data structures, you can always try the binary search guess-
and-check technique (pg 338).
Finally, we implement the sliding window. We recommend following some conventions such as those
described on page 511. Here are some edge cases to keep an eye for:
• Make sure to consider the case where no valid window is found, especially for minimization problems.
• Make sure not to shrink an empty window or grow a window past the end of the array.
Even better, update your own personal Bug List (pg 172) with the edge cases that you tend to forget about.
Counting problems are less common, but we can use the trick to adapt the maximum window recipe to
At-Most-K counting problems (pg 530). For Exactly-K/At-Least-K counting problems, see the reusable idea
for transforming them to At-Most-K problems (pg 532).
For maximization problems without the maximum window property (pg 523) or minimization problems
without the minimum window property (pg 526), sliding windows may be the wrong technique! Consider
other techniques commonly used on subarray problems, like two pointers, prefix sums, and dynamic
programming.
536 B E YO N D CR ACK I N G T H E CO D I N G I N T E R V I E W ▸ C ATA LO G O F T E CH N I C A L TO PI C S
Sliding window triggers: The input type is just an array of numbers or a string, and maybe a
number The lower bound is linear
Keywords: subarray, substring, length, contiguous, consecutive, range, longest, or shortest
At this point, you should be ready to start adding sliding window problems to your practice rotation You can
find the problems in this chapter and additional problems in the companion AI interviewer
ONLINE RESOURCES
Online resources for this chapter include:
• A chance to try each problem in this chapter in AI Interviewer
• Interview replays that show specific mistakes people make with sliding windows
problems
• Full code solutions for every problem in the chapter in multiple programming
languages
Try online at bctci co/sliding-windows
View online materials for Beyond Cracking the Coding Interview at bctci.co