DEV Community

Cover image for How I Went from Struggling to Beating 100% of Submissions
Ahmed Rakan
Ahmed Rakan

Posted on

How I Went from Struggling to Beating 100% of Submissions

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

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)