I used to hate LeetCode problems. Now I enjoy them. I actually start my day with a problem and end it with one. In the morning, it wakes up my brain for more complex problem-solving. Late at night, it drains whatever mental energy I have left so I can sleep well.
Recently, I've noticed something interesting: I regularly achieve faster-than-100% runtime on problems that have been around for decades with millions of submissions from really smart people—students from Harvard, MIT, and other prestigious universities. I can solve these in minutes, whereas a few years ago (right after I graduated from college), I'd spend hours on a single problem.
You might think it's just experience. You're partially right. But three years ago, I completely changed my approach to algorithms and data structures.
Instead of grinding through hundreds of problems, I started recognizing patterns. After a while, I could work with complex data structures effortlessly.
That said, I sometimes totally forget how to implement even a simple sorting algorithm, and that's fine. That's where consistent review comes in handy when prepping for interviews.
My Three-Step Approach
Step 1: Build solid fundamentals in core data structures and algorithms.
Step 2: Learn how to read questions and truly understand the entire problem. This is half the solution.
Step 3: Start with a brute force solution, then optimize.
Example: LeetCode 25 - Reverse Nodes in k-Group
Let me show you how I tackled this problem today.
The Problem: Given a linked list, reverse the first k nodes, then repeat for the next k nodes, and so on. Only reverse if there are at least k nodes remaining. If fewer than k nodes remain, leave them as-is.
Examples:
Input: head = [1,2,3,4,5], k = 2 → Output: [2,1,4,3,5]
Input: head = [1,2,3,4,5], k = 3 → Output: [3,2,1,4,5]
Iteration 1: Brute Force (O(N²))
My first instinct was to convert the linked list to an array, process it, then rebuild the linked list:
class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
# Convert linked list to array
res = []
while head:
res.append(head.val)
head = head.next
# Process in k-sized chunks
l = 0
r = k
res2 = []
while r <= len(res):
temp = res[l:r][::-1]
res2.extend(temp) # Bottleneck: O(k) per iteration
r += k
l += k
# Handle tail if less than k elements
if len(res) != len(res2):
res2.extend(res[l:])
# Rebuild linked list
dummy = ListNode()
curr = dummy
for val in res2:
curr.next = ListNode(val)
curr = curr.next
return dummy.next
Problem: The list.extend() operation inside the loop makes this O(N²) because we're repeatedly extending the list.
Iteration 2: Optimized to O(N)
Instead of using extend() in a loop, I process elements directly:
class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
# Convert to array
res = []
while head:
res.append(head.val)
head = head.next
res2 = []
n = len(res)
# Process in k-sized chunks
for i in range(0, n, k):
if i + k <= n:
# Reverse this chunk
for j in range(i + k - 1, i - 1, -1):
res2.append(res[j])
else:
# Copy remaining elements as-is
res2.extend(res[i:])
break
# Rebuild linked list
dummy = ListNode()
curr = dummy
for val in res2:
curr.next = ListNode(val)
curr = curr.next
return dummy.next
We still use extend(), but only once at the end, so this is O(N).
Iteration 3: Eliminate the Third Pass
We can eliminate res2 entirely by building the result linked list directly in the second loop:
class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
# Convert to array
res = []
while head:
res.append(head.val)
head = head.next
n = len(res)
# Build result directly
dummy = ListNode()
curr = dummy
for i in range(0, n, k):
if i + k <= n:
# Reverse this chunk
for j in range(i + k - 1, i - 1, -1):
curr.next = ListNode(res[j])
curr = curr.next
else:
# Copy remaining elements as-is
for val in res[i:]:
curr.next = ListNode(val)
curr = curr.next
break
return dummy.next
Result: O(N) time, O(N) space. Clean and efficient.
Can You Optimize Further? Yes ( space-wise we don't need res, use pointers to miniplate the same list )
Key Takeaway
The real skill isn't memorizing solutions—it's learning to:
- O-notation ( time & space complexity )
- Having solid understanding of key DS & A and how to work with them, what problems they solve.
- Start with a working brute force solution
- Identify bottlenecks systematically
- Optimize iteratively
- Practice on one key DS, Algorithm until it becomes like bubble sort or array then move on.
This pattern recognition approach has transformed my problem-solving speed and enjoyment of algorithmic challenges.
Best,
Ahmed
Top comments (0)