Lesson 4

Welcome to this unit's coding session! We're going to take a deep dive into an exciting technique — the **two-pointer technique**. This technique is a crucial skill for enhancing your algorithmic problem-solving abilities, especially when dealing with arrays. In this unit, we'll apply this technique to a problem that involves an array of integers and a target value. Let's get started!

Envision the problem at hand: we've been given an array of distinct integers and a target value. The task is to find all pairs of integers from the given array that sum up to the target value using the two-pointer technique. The function `findPairs`

should take this array of integers and a target value as parameters. It should return an `ArrayList`

containing `Map.Entry`

pairs of numbers, sorted in ascending order by the first element of each pair. If no pairs satisfy this requirement, the function should return an empty `ArrayList`

.

Consider, for instance, an example in which you're given an array, `int[] numbers = {1, 3, 5, 2, 8, -2}`

, and `target = 6`

. In this case, the function should return `[{ -2, 8 }, { 1, 5 }]`

because only these pairs from the input array of integers add up to the target value.

The naive approach to solve this problem would be to use a pair of nested loops to check each pair of numbers.

This would have a time complexity of $O(n^2)$ and a space complexity of $O(n)$. The naive approach can be time-consuming and inefficient, particularly for large arrays.

Comparatively, the **two-pointer technique** makes the problem-solving process more efficient by eliminating unnecessary operations (repeatedly checking pairs that can't sum to the target), thus enhancing the overall performance and efficiency of the solution.

We first sort the array and then apply the two-pointer technique:

Two pointers, one at the start (`left`

) and the other at the end (`right`

), are initialized.

In each iteration, we calculate the sum of the two numbers pointed to by the two respective pointers. If the sum is equal to the target value, the pair is added to the output list, and both pointers are moved towards the center. This is because we know there are no other potential pairs with the current values of `numbers[left]`

and `numbers[right]`

(since the numbers are distinct).

When the sum is less than the target, we move the left pointer to the right (increasing the value of `numbers[left]`

), and when the sum is greater than the target, we move the right pointer to the left (decreasing the value of `numbers[right]`

). This process continues until the left pointer crosses the right pointer. At this point, all potential pairs have already been identified and added to the return list.

The time complexity of this solution is $O(n \log n)$ due to the sorting of the array. After the sorting operation, the two-pointer traversal is linear, so it doesn't affect the overall time complexity. Sorting is the most expensive operation in this case.

The space complexity is $O(n)$ because in the worst-case scenario (when all elements form a pair summing up to the target), the return list would require space for `n/2`

pairs.

The first step to addressing this problem is sorting the array. By sorting the array of integers, we can use the two-pointer technique effectively, as we know that values on the left are always smaller, while those on the right are always larger. Let's start building our function with this step:

Java`1import java.util.ArrayList; 2import java.util.Arrays; 3import java.util.AbstractMap.SimpleEntry; 4import java.util.List; 5import java.util.Map.Entry; 6 7public class Main { 8 public static List<Entry<Integer, Integer>> findPairs(int[] numbers, int target) { 9 Arrays.sort(numbers); 10 return new ArrayList<>(); 11 } 12}`

Once the array is sorted, let's move on to the next stage. We'll initialize two pointers — one at the start of the array, `left`

, and the other at the end of the array, `right`

. We'll also set up an empty `ArrayList`

, `pairs`

, to store our potential number pairs.

Java`1import java.util.ArrayList; 2import java.util.Arrays; 3import java.util.AbstractMap.SimpleEntry; 4import java.util.List; 5import java.util.Map.Entry; 6 7public class Main { 8 public static List<Entry<Integer, Integer>> findPairs(int[] numbers, int target) { 9 Arrays.sort(numbers); 10 int left = 0; 11 int right = numbers.length - 1; 12 List<Entry<Integer, Integer>> pairs = new ArrayList<>(); 13 return pairs; 14 } 15}`

The most challenging and exciting part is the formation of pairs. We'll run a while loop until the left pointer crosses the right one. In each iteration, if the total equals the target, we'll store the pair and move one step from both ends. If the total is smaller than the target, we'll move one step to the right from the left, seeking a larger value. Conversely, if the total is larger than the target, we'll move one step to the left from the right, seeking a smaller value. Here is how it could be done:

Java`1import java.util.ArrayList; 2import java.util.Arrays; 3import java.util.AbstractMap.SimpleEntry; 4import java.util.List; 5import java.util.Map.Entry; 6 7public class Main { 8 public static List<Entry<Integer, Integer>> findPairs(int[] numbers, int target) { 9 Arrays.sort(numbers); 10 int left = 0; 11 int right = numbers.length - 1; 12 List<Entry<Integer, Integer>> pairs = new ArrayList<>(); 13 14 while (left < right) { 15 int total = numbers[left] + numbers[right]; 16 17 if (total == target) { 18 pairs.add(new SimpleEntry<>(numbers[left], numbers[right])); 19 left++; 20 right--; 21 } else if (total < target) { 22 left++; 23 } else { 24 right--; 25 } 26 } 27 return pairs; 28 } 29 30 public static void main(String[] args) { 31 int[] numbers = {1, 3, 5, 2, 8, -2}; 32 List<Entry<Integer, Integer>> result = findPairs(numbers, 6); 33 for (Entry<Integer, Integer> pair : result) { 34 System.out.println(pair.getKey() + " " + pair.getValue()); 35 } 36 } 37}`

Congratulations on successfully handling a problem that requires the imperative **two-pointer technique**! This scenario involved understanding and meticulously applying the two-pointer technique to identify all relevant pairs of integers. In this task, we learned how summarizing concepts can ultimately aid us in finding a solution more efficiently, without having to approach each element explicitly.

Now, it's time for some practice! In the subsequent practice session, you'll handle similar problems that demand the use of the two-pointer method. Take everything you've learned from this lesson and apply it directly to the practice! Thank you for attending the session, and keep up the excellent work!