Skill Set - Two Pointers


Skills for tackling LeetCode problems - Two Pointers.

Problem Statement: Given an array of sorted numbers and a target sum, find a pair in the array whose sum is equal to the given target.

We can start with one pointer in the beginning and another pointer at the end. During iteration, we see if the numbers pointed by two pointers add up to the target. If not, we will move either one of the two pointers in this way 1) if the sum is greater than the target, move the end pointer to the left 2) if the sum is smaller than the target, move the start pointer to the right. The time complexity is O(n).

1. Pair with Target Sum

Given an array of sorted numbers and a target sum, find a pair in the array whose sum is equal to the given target.

# 1. Binary Search. 
# Time complexity O(N). Space complexity O(1).
def pair_with_targetsum(arr, target_sum): 
    l = 0
    r = len(arr) -1 
    while l < r:
        SUM = arr[l] + arr[r]
        if SUM == target_sum:
            return [l, r]
        if SUM > target_sum:
            r -= 1
        else:
            l += 1
    return [-1, -1]
# 2. HashTable. 
# Time complexity O(N). Space complexity O(N).
def pair_with_targetsum(arr, target_sum):
    nums = {}
    for i, num in enumerate(arr):
        if target_sum - num in nums:
            return [nums[target_sum - num], i]
        else:
            nums[arr[i]] = i
    return [-1. -1]

2. Remove Duplicates

Given an array of sorted numbers, remove all duplicates from it. You should not use any extra space; after removing the duplicates in-place return the length of the subarray that has no duplicate in it.

# Shift the elements left whenever we encounter duplicates.
# Time complexity O(N). Space complexity O(1).
def remove_duplicates(arr):
    next_unique = 1
    for i in range(1, len(arr)):
        if arr[next_unique - 1] != arr[i]:
            arr[next_unique] = arr[i]
            next_unique += 1
    return next_unique

Given an unsorted array of numbers and a target “key”, remove all instances of “key” in-place and return the new length of the array.

# Shift the elements left whenever we encounter duplicate of key.
# Time complexity O(N). Space complexity O(1).
def remove_element(arr, key):
    next_notKey = 0
    for i in range(len(arr)):
        if arr[i] != key:
            arr[next_notKey] = arr[i]
            next_notKey += 1
    return next_notKey

3. Squaring a Sorted Array

Given a sorted array, create a new array containing squares of all the numbers of the input array in the sorted order.

# One pointer squares non-negative numbers, the other pointer squares negative numbers.
# Time complexity O(N). Space complexity O(N).
def make_squares(arr):
    l, r = 0, len(arr) - 1
    res = []
    while l <= r:
        leftSquare = arr[l] * arr[l]
        rightSquare = arr[r] * arr[r]
        
        if leftSquare > rightSquare:
            res.insert(0, leftSquare)
            l += 1
        else:
            res.insert(0, rightSquare)
            r -= 1
    return res

4. Triplet Sum to Zero

Given an array of unsorted numbers, find all unique triplets in it that add up to zero.

# Sort the array to handle duplicates. Find Y + Z = - X. 
# Time complexity O(N log N + N^2) = O(N^2). (O(N log N) for sort(), O(N^2) for searching) 
# Space complexity O(N) required for sorting. 
def search_triplets(arr):
    arr.sort()
    triplets = []
    
    for i in range(len(arr)):
        if i > 0 and arr[i] == arr[i-1]: # skip duplicate
            continue

        right = len(arr) - 1
        target_sum = -arr[i] # - X
        left = i + 1
        
        while left < right:
            current_sum = arr[left] + arr[right] # Y + Z
            
            if current_sum == target_sum:
                triplets.append([-target_sum, arr[left], arr[right]])
                left += 1
                right -= 1
                while left < right and arr[left] == arr[left - 1]: left += 1
                while left < right and arr[right] == arr[right + 1]: right -= 1
            elif target_sum > current_sum:
                left += 1
            else:
                right -= 1

    return triplets

5. Triplet Sum Close to Target

Given an array of unsorted number and a target number, find a triplet in the array whose sum is as close to the target number as possible, return the sum of the triplet. If there are more than one such triplet, return teh sum of the triplet with the smallest sum.

# Time complexity O(N log N + N^2) = O(N^2) (sorting takes O(N log N), searching takes O(N^2)). 
# Space complexity O(N).
import math
def triplet_sum_close_to_target(arr, target_sum):
    arr.sort()
    MIN_diff = math.inf

    for i in range(len(arr) - 2):
        left = i + 1
        right = len(arr) -1
    
        while left < right:
            target_diff = target_sum - (arr[i] + arr[left] + arr[right]) # save the difference between the triplet and the target
    
            if target_diff == 0: return target_sum # find the triplet
    
            if abs(target_diff) < abs(MIN_diff) or (abs(target_diff) == abs(MIN_diff) and target_diff > MIN_diff):
                MIN_diff = target_diff
    
            if target_diff > 0: # target_sum is greater than sum of triplet
                left += 1
            else: # target_sum is smaller than sum of triplet
                right -= 1

    return target_sum - MIN_diff # return the sum with the smallest difference

6. Triplet with Smaller Sum

Given an array arr of unsorted numbers and a target sum, count all triplets in it such that arr[i] + arr[j] + arr[k] < target where i, j and k are three different indices. Write a functions to return the count of such triplets.

# Time complexity O(N^2) (Sorting takes O(N log N), searching takes O(N^2)). 
# Space complexity O(N).
def triplet_with_smaller_sum(arr, target):
    arr.sort()
    count = 0
    for i in range(len(arr) - 2):
        left = i + 1
        right = len(arr) - 1
        while left < right:
            if arr[left] + arr[right] < target - arr[i]:
                                # since arr is sorted, we can replace arr[right] with any number between right and left, so we count (right - left) number of triplets.
                  count += right - left 
                left += 1 # need a pair with a larger sum
            else:
                right -= 1 # need a pair with a smaller sum
    return count

What if we are asking to return a list of all triplets,

# Time complexity O(N^3) (Sorting takes O(N log N), searching takes O(N^3)). 
# Space complexity O(N).
def triplet_with_smaller_sum(arr, target):
    arr.sort()
    triplets = [] # create a list to store the answers
    # count = 0
    for i in range(len(arr) - 2):
        left = i + 1
        right = len(arr) - 1
        while left < right:
            if arr[left] + arr[right] < target - arr[i]:
                for j in range(right, left, -1):
                    triplets.append([arr[i], arr[left], arr[j]])
                # count += right - left
                left += 1
            else:
                right -= 1
    return triplets

7. Subarrays with Product Less than a Target

Given an array with positive numbers and a positive target number, find all of its continuous subarrays whose product is less than the target number.

# Time complexity O(N^3) (Main for-loop takes O(N), finding subarrays takes O(N^2)). 
# Space complexity O(N) for tmp list.
import collections
def find_subarrays(arr, target):
    result = []
    product = 1
    left = 0

    for right in range(len(arr)):
        product += arr[right]
        
        while product >= target and left < len(arr):
            product /= arr[left]
            left += 1
        tmp = collections.deque()

        # product from left to right is less than the target
        # so all subarrays from left to right are less than the target
        for i in range(right, left - 1, -1):
            tmp.appendleft(arr[i])
            result.append(list(tmp))
            
    return result

8. Dutch National Flag Problem

Given an array containing 0s, 1s and 2s, sort the array in-place. You should treat numbers of the array as objects, hence, we cannot count 0s, 1s, and 2s to recreate the array.

# Time complexity O(N). Space complexity O(1)
def dutch_flag_sort(arr):
    i = low = 0
    high = len(arr) - 1
    while i <= high:
        if arr[i] == 0:
            arr[i], arr[low] = arr[low], arr[i] # move 0s before "low" pointer
            i += 1
            low += 1
        elif arr[i] == 1:
            i += 1
        else:
            arr[i], arr[high] = arr[high], arr[i] # move 1s after "high" pointer
            high -= 1

  TOC