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
456: Whiteboard Practice: Two-Pointer Techniques
Whenever I'm prepping for a whiteboard interview, there's one pattern that always comes up: the Two-Pointer technique. It sounds fancy, but it's really just a way to avoid those nested for loops that make your time complexity skyrocket to $O(n^2)$. Let's walk through a real scenario and see how I'd actually solve it if we were sitting together at a whiteboard.
The brute force struggle
Imagine we have a sorted list of integers, and we need to find two numbers that add up to a specific target. Let's use [1, 2, 3, 4, 6, 8, 11] and a target of 10. My first instinct—the "lazy" way—is usually to just check every possible pair.
def find_sum_naive(nums, target):
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
return (nums[i], nums[j])
return None
This works, sure. But look at those nested loops. If the list has 10,000 elements, we're potentially doing millions of additions. It's inefficient. But here's the key detail I almost ignored: the list is already sorted. In a whiteboard setting, whenever you see "sorted array," a lightbulb should go off in your head. The sorting is a hint that we can do better than $O(n^2)$.
Trying to optimize on the fly
I want to find a way to "scan" the list without restarting the inner loop every time. If I start at the beginning with 1, I know I need a 9 to hit my target. Since the list is sorted, I could binary search for 9, but that's a bit clunky to implement.
What if I look at both ends at once? Let's trace this manually. My left pointer (L) is at the start (1), and my right pointer (R) is at the end (11).
- L=1, R=11: Sum is 12. That's too high. Since the list is sorted, I know that any other pair involving
11is also going to be too high (because1is the smallest possible partner). So,11is useless to me. I'll move my right pointer one step left. - L=1, R=8: Sum is 9. Now it's too low. I need to increase the sum. Since
8is the largest remaining number, the only way to get a bigger sum is to move my left pointer one step right. - L=2, R=8: Sum is 10. Found it.
By moving the pointers inward, I only traversed the list once. That's $O(n)$ time complexity. It's a massive improvement.
Turning the logic into code
Now I just need to translate that "squeezing" motion into a while loop. The most important part here is the condition: the pointers should stop if they meet or cross. If they cross, it means we've checked every viable pair and no solution exists.
def find_sum_two_pointer(nums, target):
left = 0
right = len(nums) - 1
while left < right:
current_sum = nums[left] + nums[right]
if current_sum == target:
return (nums[left], nums[right])
elif current_sum < target:
# We need a larger sum, move the left pointer forward
left += 1
else:
# We need a smaller sum, move the right pointer backward
right -= 1
return None
I like this approach because it's clean. There's no complex indexing or nested logic. You're just narrowing the window of possibility until you either hit the target or run out of elements. This "converging pointers" pattern is the bread and butter of array manipulation problems.
📋 Practical Task
Exercise: The Alphanumeric Palindrome Validator
You've mastered the converging pointers technique for numbers. Now, let's apply it to strings. Your task is to write a function is_palindrome(s) that checks if a string is a palindrome, but with a catch: you must ignore case and ignore all non-alphanumeric characters (spaces, punctuation, etc.).
Requirements:
- Do not create a new "cleaned" version of the string first (which would use $O(n)$ extra space). Instead, use two pointers to skip over invalid characters on the fly.
- One pointer should start at the beginning, and one at the end.
- Move the pointers inward, skipping any character that
.isalnum()returnsFalsefor. - Compare the characters; if they don't match, return
Falseimmediately.
Test Case: "A man, a plan, a canal: Panama" should return True.
There are no comments for now.