Comic: How to find three numbers whose sum is a "specific value" in an array?

Some time ago, we introduced a classic algorithm problem on LeetCode [the sum of two numbers problem] .

This time, we expand the problem a bit and try to find three numbers in the array that sum to a "specific value" .

What are the specific requirements of the topic? Given the following integer array:

We arbitrarily choose a specific value, such as 13, and ask to find all combinations where the sum of three numbers is equal to 13 .

Since 5+6+2=13, 5+1+7=13, 3+9+1=13, the final output result is as follows:

【5, 6,2】

【5, 1,7】

【3, 9,1】


Xiao Hui's idea is to transform the original "three-number sum problem" into a "two-number sum problem" n times.

Let's take the above array as an example, select a specific value of 13, and demonstrate Xiao Hui's specific ideas:

In the first round , visit the first element 5 of the array, and turn the problem into finding two numbers that sum to 8 (13-5) from the following elements:

How to find two numbers whose sum is 8? According to what we said last time, we can use a hash table to efficiently solve:

In the second round , visit the second element 12 of the array, and convert the problem into finding two numbers that sum to 1 (13-12) from the following elements:

In the third round , visit the third element 6 of the array, and turn the problem into finding two numbers that sum to 7 (13-6) from the following elements:

By analogy, traversing the entire array all the time is equivalent to solving the sum of two numbers problem n times.

    public static List<List<Integer>> threeSum(int[] nums, int target) {
        List<List<Integer>> resultList = new ArrayList<>();
        for (int i = 0; i < nums.length; i++) {
            Map<Integer, Integer> map = new HashMap<>();
            int d1 = target - nums[i];
            //寻找两数之和等于d1的组合
            for (int j = i+1; j < nums.length; j++) {
                int d2 = d1 - nums[j];
                if (map.containsKey(d2)) {
                    resultList.add(Arrays.asList(nums[i], d2, nums[j]));
                }
                map.put(nums[j], j);
            }
        }
        return resultList;
    }

In the above code, the time complexity of solving the "Sum of Two Numbers Problem" in each round is O(n), a total of n iterations, so the total time complexity of the solution is O(n²) .

As for the space complexity, the same hash table is built repeatedly, and there are at most n-1 key-value pairs in the hash table, so the space complexity of the solution is O(n) .

We still use the previous array as an example to sort the array in ascending order:

This is a bit abstract, let’s demonstrate in detail:

In the first round , visit the first element 1 of the array and turn the problem into finding two numbers that sum to 12 (13-1) from the following elements.

How to find two numbers whose sum is 12? We set two pointers, the pointer j points to the leftmost element 2 of the remaining elements, and the pointer k points to the rightmost element 12:

Calculate the sum of the corresponding elements of the two pointers, 2+12 = 14> 12, the result is too large.

Since the array is arranged in ascending order, the element on the left of k must be less than k, so we move the pointer k to the left:

Calculate the sum of the corresponding elements of the two pointers, 2+9 = 11< 12. This time the result is too small.

The element to the right of j must be greater than j, so we move the pointer j one place to the right:

Calculate the sum of the corresponding elements of the two pointers, 3+9 = 12, which just meets the requirements!

So we successfully found a set of matching combinations: 1, 3, 9

But this is not the end, we have to continue to find other combinations, let the pointer k continue to move left:

 Calculate the sum of the corresponding elements of the two pointers, 3+7 = 10<12, the result is too small.

So we move the pointer j to the right:

Calculate the sum of the corresponding elements of the two pointers, 5+7 = 12, and find a group that meets the requirements:

1,5,7

We continue to search and move the pointer k to the left:

 Calculate the sum of the corresponding elements of the two pointers, 5+6 = 11<12, the result is too small.

So we move the pointer j to the right:

At this point, the two pointers are coincident, if you continue to move, it may repeat the combination found before, so we directly end the cycle.

In the second round , visit the second element 2 of the array, and convert the problem into finding two numbers that sum to 11 (13-2) from the following elements.

We still set two pointers, the pointer j points to the leftmost element 3 of the remaining elements, and the pointer k points to the rightmost element 12:

 Calculate the sum of the corresponding elements of the two pointers, 3+12 = 15> 11. The result is too large.

We let the pointer k move to the left:

 Calculate the sum of the corresponding elements of the two pointers, 3+9 = 12> 11. The result is still too large.

We let the pointer k continue to move left:

 Calculate the sum of the corresponding elements of the two pointers, 3+7 = 10 <11, the result is too small.

We move pointer j to the right:

 Calculate the sum of the corresponding elements of the two pointers, 5+7 = 12> 11. The result is too large.

We let the pointer k move to the left:

 Calculate the sum of the corresponding elements of the two pointers, 5+6 = 11, so we find a group that meets the requirements:

2,5,6

We continue to search and move the pointer k to the left:

At this point, the double pointers overlap once again, and we end the cycle.

According to this idea, we have been traversing the entire array.

In this way, two pointers are used to point to the two ends of the array and constantly move closer to the middle to find a matching combination. This is the double pointer method, also known as the "clamping method".

    public static List<List<Integer>> threeSumv2(int[] nums, int target) {
        Arrays.sort(nums);
        List<List<Integer>> resultList = new ArrayList<List<Integer>>();
        //大循环
        for (int i = 0; i < nums.length; i++) {
            int d = target - nums[i];
            // j和k双指针循环定位,j在左端,k在右端
            for (int j=i+1,k=nums.length-1; j<nums.length; j++) {
                // k指针向左移动
                while (j<k && (nums[j]+nums[k])>d) {
                    k--;
                }
                //双指针重合,跳出本次循环
                if (j == k) {
                    break;
                }
                if (nums[j] + nums[k] == d) {
                    List<Integer> list = Arrays.asList(nums[i], nums[j], nums[k]);
                    resultList.add(list);
                }
            }
        }
        return resultList;
    }

On the surface of the above code, there are three loops, but each round of pointer j and k moves at most n-1 times, so the overall time complexity of the solution is O(n²) .

The most important thing is that this solution does not use additional sets (sorting is performed directly on the input array), so the space complexity is only O(1) !

—————END—————

Friends who like this article, welcome to follow the official account  programmer Xiaohui , and watch more exciting content

点个[在看],是对小灰最大的支持!

Guess you like

Origin blog.csdn.net/bjweimengshu/article/details/109040017