DEV Community

Philip Thomas
Philip Thomas

Posted on

Demystifying Algorithms: Two Pointers

What is the Two Pointers Technique?

The Two Pointers technique is a highly efficient approach often used to solve problems that involve arrays or linked lists. It uses two pointers to traverse data in tandem, reducing unnecessary computations. This technique works particularly well for sorted data, helping you optimize solutions to problems involving pairs, triplets, or partitions.


The Technical View

  • Time Complexity: O(n) (in most cases).
    • Both pointers typically traverse the input array once, resulting in linear time complexity.
  • Space Complexity: O(1).
    • No additional data structures are used, as only a constant amount of space is required to track the pointers.

An Anorak's Compendium

The Two Pointers technique uses two markers to efficiently scan data. These pointers may move in the same direction, opposite directions, or dynamically based on specific conditions. It’s a powerful tool for working with sorted arrays or reducing the complexity of nested loops.


A Fifth-Grader's Summary

Imagine you're meeting a friend in the middle of a long rope by starting at opposite ends and walking toward each other. By working together and meeting in the middle, you avoid wasting time searching randomly.


Real-World Example

Let’s say you’re looking for a pair of socks in a sorted drawer. Instead of rummaging randomly, you start from one end of the drawer and your friend starts from the other. You meet in the middle, systematically finding the matching pair much faster.


Examples with Code, Complexity, and Optimized Patterns


1. Two Sum in a Sorted Array

Problem: Given a sorted array, find two numbers that add up to a target sum.

Code:

def two_sum_two_pointers(nums, target):
    left, right = 0, len(nums) - 1
    while left < right:
        current_sum = nums[left] + nums[right]
        if current_sum == target:
            return [left, right]
        elif current_sum < target:
            left += 1
        else:
            right -= 1
    return None

# Example Usage
nums = [2, 3, 4, 6, 8]
target = 10
print(two_sum_two_pointers(nums, target))  # Output: [1, 3]
Enter fullscreen mode Exit fullscreen mode

Complexity:

  • Time Complexity: O(n)
    • The two pointers traverse the array once.
  • Space Complexity: O(1)
    • No extra memory is used.

Optimized Pattern: Two Pointers is already optimal for this problem.


2. Reverse an Array

Problem: Reverse the elements of an array in place.

Code:

def reverse_array(nums):
    left, right = 0, len(nums) - 1
    while left < right:
        nums[left], nums[right] = nums[right], nums[left]
        left += 1
        right -= 1
    return nums

# Example Usage
nums = [1, 2, 3, 4, 5]
print(reverse_array(nums))  # Output: [5, 4, 3, 2, 1]
Enter fullscreen mode Exit fullscreen mode

Complexity:

  • Time Complexity: O(n)
    • Each element is swapped once as the two pointers meet in the middle.
  • Space Complexity: O(1)
    • The array is modified in place, requiring no additional space.

Optimized Pattern: Two Pointers is already optimal for reversing arrays.


3. Container With Most Water

Problem: Given an array representing the height of vertical lines, find two lines that together with the x-axis form a container with the maximum area.

Code:

def max_area(heights):
    left, right = 0, len(heights) - 1
    max_area = 0
    while left < right:
        current_area = min(heights[left], heights[right]) * (right - left)
        max_area = max(max_area, current_area)
        if heights[left] < heights[right]:
            left += 1
        else:
            right -= 1
    return max_area

# Example Usage
heights = [1, 8, 6, 2, 5, 4, 8, 3, 7]
print(max_area(heights))  # Output: 49
Enter fullscreen mode Exit fullscreen mode

Complexity:

  • Time Complexity: O(n)
    • The two pointers traverse the array once.
  • Space Complexity: O(1)
    • No additional memory is used.

Optimized Pattern: Two Pointers is already optimal for this problem.


4. Is Subsequence

Problem: Check if a string s is a subsequence of string t.

Code:

def is_subsequence(s, t):
    s_ptr, t_ptr = 0, 0
    while s_ptr < len(s) and t_ptr < len(t):
        if s[s_ptr] == t[t_ptr]:
            s_ptr += 1
        t_ptr += 1
    return s_ptr == len(s)

# Example Usage
s = "abc"
t = "ahbgdc"
print(is_subsequence(s, t))  # Output: True
Enter fullscreen mode Exit fullscreen mode

Complexity:

  • Time Complexity: O(n)
    • Both pointers traverse their respective strings once.
  • Space Complexity: O(1)
    • Only a constant amount of memory is used.

Optimized Pattern: Two Pointers is already optimal for this problem.


5. Merging Two Sorted Arrays

Problem: Merge two sorted arrays into a single sorted array.

Code:

def merge_sorted_arrays(nums1, nums2):
    result = []
    i, j = 0, 0
    while i < len(nums1) and j < len(nums2):
        if nums1[i] < nums2[j]:
            result.append(nums1[i])
            i += 1
        else:
            result.append(nums2[j])
            j += 1
    result.extend(nums1[i:])
    result.extend(nums2[j:])
    return result

# Example Usage
nums1 = [1, 3, 5]
nums2 = [2, 4, 6]
print(merge_sorted_arrays(nums1, nums2))  # Output: [1, 2, 3, 4, 5, 6]
Enter fullscreen mode Exit fullscreen mode

Complexity:

  • Time Complexity: O(n + m)
    • Both pointers traverse their respective arrays once.
  • Space Complexity: O(n + m)
    • The merged array requires extra space.

Optimized Pattern: Two Pointers is already optimal for merging sorted arrays.


Conclusion

The Two Pointers technique is a versatile tool for solving problems efficiently with linear time complexity and minimal space requirements. It is especially powerful for tasks involving sorted data or problems that naturally divide into sections.

By mastering Two Pointers, you’ll gain a key optimization technique that is applicable to many real-world and algorithmic problems.

Top comments (0)