Afasari

(Re)Start Learning Data Structure and Algorithm - Part 4, Two Pointers

LeetCode 125: Valid Palindrome

Β 1. Title & Problem Goal

  1. Analysis & Constraints
  1. Approaches Comparison Table
Approach Logic Time Complexity Space Complexity Pros/Cons
New String + Reverse Filter out non-alphanumeric chars, lowercase the string, then compare with its reverse. 𝑂(𝑛) 𝑂(𝑛) Very easy to implement; uses extra memory for the copy.
Two Pointers (Optimal) Use two pointers (left, right). Skip invalid chars and compare values at each step. 𝑂(𝑛) 𝑂(1) Most Optimized. No extra memory allocated.
  1. Testing Strategy

Standard Cases:

Edge Cases (Boundaries):

Negative Cases:

  1. Optimal Code & Why (Golang)

Why this is optimal: This approach avoids creating a new string or slice, which saves 𝑂(𝑛) memory. It processes the string in a single pass (𝑂(𝑛)) using the original byte underlying the string.

func isPalindrome(s string) bool {
    l, r := 0, len(s)-1

    for l < r {
        // 1. Move left pointer if not alphanumeric
        if !isAlphanumeric(s[l]) {
            l++
            continue
        }
        // 2. Move right pointer if not alphanumeric
        if !isAlphanumeric(s[r]) {
            r--
            continue
        }
        // 3. Compare characters (convert to lowercase)
        if toLower(s[l]) != toLower(s[r]) {
            return false
        }
        l++
        r--
    }
    return true
}

// Helper: Check if byte is letter or number
func isAlphanumeric(b byte) bool {
    return (b >= 'a' && b <= 'z') ||
           (b >= 'A' && b <= 'Z') ||
           (b >= '0' && b <= '9')
}

// Helper: Standardize to lowercase
func toLower(b byte) byte {
    if b >= 'A' && b <= 'Z' {
        return b + ('a' - 'A')
    }
    return b
}
  1. Reflection / Mistake Log

LeetCode 167: Two Sum II - Input Array Is Sorted

Β 1. Title & Problem Goal

  1. Analysis & Constraints (2026 Data)
  1. Approaches Comparison Table
Approach Logic Time Complexity Space Complexity Pros/Cons
Brute Force Nested loops checking every pair. 𝑂(𝑛^2) 𝑂(1) Too slow; ignores the sorted property.
Binary Search For each π‘₯, binary search for (π‘‘π‘Žπ‘Ÿπ‘”π‘’π‘‘ βˆ’ π‘₯) in the remaining array. 𝑂(𝑛log𝑛) 𝑂(1) Better, but not the most efficient for sorted arrays.
Two Pointers Start pointers at left and right. Adjust based on whether current sum is too high or too low. 𝑂(𝑛) 𝑂(1) Most Optimized. Uses the sorted property perfectly.
  1. Testing Strategy

Standard Cases:

Edge Cases (Boundaries):

Negative Cases:

  1. Optimal Code & Why (Golang)

Why this is optimal: The Two-Pointer technique takes advantage of the sorted array. If the sum < target, we must move the left pointer right to get a larger value. If sum > target, we must move the right pointer left to get a smaller value. This ensures we find the solution in one linear pass with zero extra memory allocation.

func twoSum(numbers []int, target int) []int {
    // 1. Initialize two pointers at extremes
    left := 0
    right := len(numbers) - 1

    for left < right {
        currentSum := numbers[left] + numbers[right]

        if currentSum == target {
            // 2. Return 1-indexed results
            return []int{left + 1, right + 1}
        }

        if currentSum < target {
            // 3. Sum too small, move left pointer right for larger value
            left++
        } else {
            // 4. Sum too large, move right pointer left for smaller value
            right--
        }
    }

    return []int{} // Should not be reached per constraints
}
  1. Reflection / Mistake Log

LeetCode 15: 3Sum

Β 1. Title & Problem Goal

  1. Analysis & Constraints (2026 Data)
  1. Approaches Comparison Table
Approach Logic Time Complexity Space Complexity Pros/Cons
Brute Force Three nested loops checking every combination. 𝑂(𝑁^3) 𝑂(1) Way too slow; hard to handle duplicates.
Hash Map Fix 𝑖 and 𝑗, look for βˆ’(𝑖+𝑗) in a map. 𝑂(𝑁^2) 𝑂(𝑁) Hard to manage unique triplets without a Set.
Sort + Two Pointers Fix 𝑖, then use Two Pointers for 𝑗 and π‘˜ on the remaining sorted array. 𝑂(𝑁2) 𝑂(1) to 𝑂(𝑁) Optimal. Easiest way to skip duplicates and maintain 𝑂(1) extra space.
  1. Testing Strategy

Standard Cases:

Edge Cases (Boundaries):

Negative Cases:

  1. Optimal Code & Why (Golang)

Why this is optimal: Sorting allows us to turn the problem into multiple β€œTwo Sum II” problems. By skipping identical values for the fixed element and the pointers, we handle the β€œunique triplets” requirement without needing a memory-heavy Set.

func threeSum(nums []int) [][]int { // Returning [][]int
 sort.Ints(nums)
 res := [][]int{}

 for i := 0; i < len(nums)-2; i++ {
  // 1. Skip duplicates for the first element
  if i > 0 && nums[i] == nums[i-1] {
   continue
  }

  // 2. Standard Two-Pointer approach
  l, r := i+1, len(nums)-1
  for l < r {
   sum := nums[i] + nums[l] + nums[r]
   if sum < 0 {
    l++
   } else if sum > 0 {
    r--
   } else {
    res = append(res, []int{nums[i], nums[l], nums[r]})
    // 3. Skip duplicates for left and right pointers
    for l < r && nums[l] == nums[l+1] {
     l++
    }
    for l < r && nums[r] == nums[r-1] {
     r--
    }
    l++
    r--
   }
  }
 }
 return res
}
  1. Reflection / Mistake Log

LeetCode 11: Container With Most Water

Β 1. Title & Problem Goal

  1. Analysis & Constraints (2026 Data)
  1. Approaches Comparison Table
Approach Logic Time Complexity Space Complexity Pros/Cons
Brute Force Nested loops check every possible pair of lines. 𝑂(𝑛2) 𝑂(1) Simple but unusable for large 𝑛
Two Pointers (Optimal) Start at extremes. Always move the pointer pointing to the shorter line. 𝑂(𝑛) 𝑂(1) Most Optimized. Guarantees finding the max in one pass.
  1. Testing Strategy

Standard Cases:

Edge Cases (Boundaries):

Negative Cases:

  1. Optimal Code & Why (Golang)

Why this is optimal: The logic is Greedy. We start with the maximum possible width. To potentially find a larger area, we must replace the shorter side, because the area is limited by the shorter side. Moving the taller side would only decrease width without increasing the bottleneck height.

func maxArea(height []int) int {
    maxArea := 0
    left := 0
    right := len(height) - 1

    for left < right {
        // 1. Calculate current area
        w := right - left
        h := 0
        if height[left] < height[right] {
            h = height[left]
        } else {
            h = height[right]
        }

        currentArea := w * h

        // 2. Update global max
        if currentArea > maxArea {
            maxArea = currentArea
        }

        // 3. Move the pointer pointing to the shorter line
        // This is the greedy step to find a taller bottleneck
        if height[left] < height[right] {
            left++
        } else {
            right--
        }
    }

    return maxArea
}
  1. Reflection / Mistake Log

LeetCode 42: Trapping Rain Water

Β 1. Title & Problem Goal

  1. Analysis & Constraints (2026 Data)
  1. Approaches Comparison Table
Approach Logic Time Complexity Space Complexity Pros/Cons
Brute Force For each bar, scan left and right to find max heights. 𝑂(𝑛^2) 𝑂(1) Simple but hits TLE.
Dynamic Programming Pre-compute leftMax and rightMax arrays. 𝑂(𝑛) 𝑂(𝑛) Very easy to understand; uses extra memory.
Two Pointers (Optimal) Move pointers from both ends; maintain leftMax and rightMax on the fly. 𝑂(𝑛) 𝑂(1) Most Optimized. Best for high-performance 2026 systems.
Monotonic Stack Use a stack to track bounded β€œbasins” as you iterate. 𝑂(𝑛) 𝑂(𝑛) Useful for similar β€œarea” problems; harder to visualize.
  1. Testing Strategy

Standard Cases:

Edge Cases (Boundaries):

Negative Cases:

  1. Optimal Code & Why (Golang)

Why this is optimal: The Two-Pointer approach eliminates the need for auxiliary arrays. By always moving the pointer with the smaller maximum height, we can guarantee that the water trapped at that position is limited by that smaller maximum, regardless of what lies between the two pointers.

func trap(height []int) int {
 if len(height) == 0 {
  return 0
 }

 left, right := 0, len(height)-1
 leftMax, rightMax := height[left], height[right]
 totalWater := 0

 for left < right {
  // We can only trap water based on the shorter side
  if leftMax < rightMax {
   left++
   // Update leftMax
   if height[left] > leftMax {
    leftMax = height[left]
   } else {
    // Trapped water = boundary - current height
    totalWater += leftMax - height[left]
   }
  } else {
   right--
   // Update rightMax
   if height[right] > rightMax {
    rightMax = height[right]
   } else {
    totalWater += rightMax - height[right]
   }
  }
 }

 return totalWater
}
  1. Reflection / Mistake Log

This post is part of a series: (Re)Start Learning DSA

  1. (Re)Start Learning Data Structure and Algorithm - Part 0, Introduction
  2. (Re)Start Learning Data Structure and Algorithm - Part 1, Complexities
  3. (Re)Start Learning Data Structure and Algorithm - Part 2, Mathematics
  4. (Re)Start Learning Data Structure and Algorithm - Part 3, Array
  5. (Re)Start Learning Data Structure and Algorithm - Part 4, Two Pointers (this post)
  6. (Re)Start Learning Data Structure and Algorithm - Part 5, Stack