Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
457: Whiteboard Practice: Sliding Window Techniques
If you've spent any time prepping for technical interviews or optimizing data processing pipelines, you've probably encountered a problem where you need to analyze a "chunk" of an array or string. The instinct for most of us—myself included, early in my career—is to just grab that chunk, process it, and then move one step over and do it all over again. It feels intuitive. But in the world of Big O, that intuition is usually a trap.
The temptation to recount everything
Let's say we're tasked with finding the maximum sum of any contiguous subarray of size k. If I'm glancing at this on a whiteboard, the "naive" approach is to loop through the list, and for every single index, slice the list to get the next k elements and sum them up. It looks something like this:
def max_sum_naive(arr, k):
max_val = 0
for i in range(len(arr) - k + 1):
# We are summing k elements every single time
current_sum = sum(arr[i:i+k])
max_val = max(max_val, current_sum)
return max_val
At first glance, this is clean. It's readable. But here is the problem: we are doing a massive amount of redundant work. If k is 1,000, we sum 1,000 elements, move one index to the right, and then sum 999 of those same elements again, plus one new one. We're basically treating every window as a brand new problem, ignoring the fact that the window at index i and the window at index i+1 are almost identical. In terms of complexity, this is O(n * k). If both n and k are large, your program is going to crawl.
Trading a loop for a simple subtraction
The "Sliding Window" technique is really just a way of saying, "Stop recalculating things you already know." Instead of re-summing the entire window, we just subtract the element that is falling out of the back of the window and add the element that is entering through the front. It’s a constant-time update regardless of how large k is.
I like to think of it as a train moving across the tracks. The train doesn't rebuild itself every time it moves an inch; it just gains a new piece of track at the front and leaves a piece behind at the back.
def max_sum_sliding(arr, k):
if not arr or k > len(arr):
return 0
# Initial window: sum the first k elements once
window_sum = sum(arr[:k])
max_val = window_sum
for i in range(len(arr) - k):
# Subtract the element leaving (i) and add the element entering (i + k)
window_sum = window_sum - arr[i] + arr[i + k]
max_val = max(max_val, window_sum)
return max_val
Now, look at the difference. We've moved from O(n * k) to O(n). We only traverse the list essentially once. Whether k is 5 or 5,000,000, the work we do inside that loop remains the same. That is the power of this pattern.
When the window needs to be flexible
The example above is a "fixed-size" window, but you'll often see "dynamic" windows in more complex problems—like finding the shortest subarray that sums to a specific target. In those cases, you don't have a constant k. Instead, you use two pointers (a left and a right). You expand the right pointer to grow your window until you meet a certain condition, and then you shrink the left pointer to see if you can still satisfy that condition with a smaller window.
It’s a bit more mental gymnastics to track the pointers, but the core philosophy remains: never recalculate what you can simply update. If you find yourself writing a sum() or a nested loop inside a loop that is processing a contiguous range, stop and ask yourself if a sliding window could replace it.
📋 Practical Task
Exercise: Finding the Smallest Window for a Target Sum
Your task is to implement a function smallest_subarray_with_sum(arr, target). Unlike the fixed-window example in the lesson, this window must be dynamic.
Write a function that takes a list of positive integers and a target sum. The function should return the length of the smallest contiguous subarray whose sum is greater than or equal to the target. If no such subarray exists, return 0.
Requirements:
- Do not use nested loops that result in
O(n^2)complexity. Aim forO(n)using the two-pointer sliding window technique. - The input list will only contain positive integers.
Example:
smallest_subarray_with_sum([2, 3, 1, 2, 4, 3], 7) should return 2 (because the subarray [4, 3] is the smallest that sums to at least 7).
smallest_subarray_with_sum([1, 2, 3], 10) should return 0.
There are no comments for now.