Daily AC Series: Sum of Four

Keywords: Programming less github

1 Topic

Leetcode Question 18 Given an array and a target, find out that the sum of the four numbers in the array is all four unreplicated numbers of the target.

2 Violence

List<List<Integer>> result = new ArrayList<>();
if (nums.length == 4 && nums[0] + nums[1] + nums[2] + nums[3] == target)
    result.add(Arrays.asList(nums[0], nums[1], nums[2],nums[3]));
else if (nums.length > 4) 
{
    Arrays.sort(nums);
    Set<List<Integer>> resultSet = new HashSet<>();
    for(int i=0;i<nums.length-3;++i)
    {
        for(int j=i+1;j<nums.length-2;++j)
        {
            for(int k=j+1;k<nums.length-1;++k)
            {
                for(int m=k+1;m<nums.length;++m)
                {
                    if(nums[i]+nums[j]+nums[k]+nums[m] == target)
                        resultSet.add(Arrays.asList(nums[i],nums[j],nums[k],nums[m]));
                }
            }
        }
    }
    result.addAll(resultSet);
    Collections.sort(result,(t1,t2)->
    {
        if(t1.get(0) > t2.get(0))
            return 1;
        if (t1.get(0) < t2.get(0))
            return -1;
        if (t1.get(1) > t2.get(1))
            return 1;
        if (t1.get(1) < t2.get(1))
            return -1;
        if (t1.get(2) > t2.get(2))
            return 1;
        if (t1.get(2) < t2.get(2))
            return -1;
        if (t1.get(3) > t2.get(3))
            return 1;
        if (t1.get(3) < t2.get(3))
            return -1;
        return 0;
    });
}
return result;

Judge the length, then sort, go directly up to four for, and then... Good! Misfortune.

3 Optimize

3.1 Remove sorting of results

First and last sort is unnecessary, that is, later

Collections.sort(result,(t1,t2)->
{
    if(t1.get(0) > t2.get(0))
        return 1;
    if (t1.get(0) < t2.get(0))
        return -1;
    if (t1.get(1) > t2.get(1))
        return 1;
    if (t1.get(1) < t2.get(1))
        return -1;
    if (t1.get(2) > t2.get(2))
        return 1;
    if (t1.get(2) < t2.get(2))
        return -1;
    if (t1.get(3) > t2.get(3))
        return 1;
    if (t1.get(3) < t2.get(3))
        return -1;
    return 0;
});

Sorting the results is unnecessary, and although it may differ from the answers during the test, it is not necessary to sort the submitted results.

3.2 stream weight removal

Previously, HashSet was used for weighting, and a matching quaternion was added directly to the set. Now stream+distinct weighting is used:

return result.stream().distinct().collect(Collectors.toList());

3.3 Double Pointer+Maximum Minimum Pruning

You can use the idea of the sum of three numbers to fix a number and double-pointer to two numbers at each end. In this case, four numbers, select two fixed numbers, calculate their sum and treat them as one number, then you can use double-pointer.

for(int i=0;i<nums.length-3;++i)
{
     for(int j=i+1;j<nums.length-2;++j)
     {
         int m = nums[i] + nums[j];
         int left = j+1;
         int right = nums.length-1;
         while(left < right)
         {
             int temp = m + nums[left] + nums[right];
             if(temp == target)
             {
                 result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
                 --right;
                 ++left;
             }
             else if(temp > target)
                 --right;
             else
                 ++left;
         }
     }
 }

m is a fixed number. left and right are two pointers. Move the two pointers according to the sum of the three numbers and the size of the target value. The minimum pruning is the first calculation of the minimum value of the "three numbers". If it is greater than the target value, it can be skipped. The maximum pruning is the calculation of the maximum value of the "three numbers". If it is less than the target value, it skips and goes to the next cycle:

int m = nums[i] + nums[j];
int left = j+1;
int right = nums.length-1;
if(m + nums[left] + nums[left+1] > target)
    continue;
if (m + nums[right-1] + nums[right] < target)
    continue;

3.4 Submission

Uh... that's a little better.

4Come here a little faster

4.1 Initial Judgment

First, the initial judgment can be a little simpler. If the array is empty or less than 4, it returns directly.

List<List<Integer>> result = new ArrayList<>();
if (nums == null && nums.length < 4)
	return result;

4.2 Not enough at once, just a few more cuts

In the above algorithm, only one maximum and one minimum pruning is done in two layers of for, which can be done once before entering the for:

Arrays.sort(nums);
int len = nums.length;
if(
	nums[0] + nums[1] + nums[2] + nums[3] >  target 
	|| 
	nums[len-4] + nums[len-3] + nums[len-2] + nums[len-1] < target
)
    return result;
for(int i=0;i<len-3;++i)   

Note to sort first, then directly determine the maximum and minimum values of the entire array and target. Then cut it again after entering the first layer for:

for(int i=0;i<len-3;++i)
{
	if(nums[i] + nums[i+1] + nums[i+2] + nums[i+3] > target)
		break;
    if(nums[i] + nums[len-3] + nums[len-2] + nums[len-1] < target)
		continue;
    for(int j=i+1;j<len-2;++j)
}

Because the arrays are sorted in ascending order, the "leftmost" four numbers must be the minimum value. If the minimum value is greater than the target, you can break directly. However, the addition of the three rightmost numbers and nums[i] is not necessarily the maximum value, so you can only continue if it is less than the target.

4.3 Weight removal

4.3.1 Dual Pointer Weight Removal

First, in a double-pointer loop, if four numbers are found to fit the criteria, you can try moving the pointer more than once:

result.add(Arrays.asList(nums[i], nums[j], nums[left++], nums[right--]));
while(left < right && nums[left] == nums[left-1])
    ++left;
while(left < right && nums[right] == nums[right+1]) 
    --right;

Because the same value can move the pointer at once, there is no need to sum again. Uh, you can try submitting it.

Hey, no, you've done so much, not so fast. Why? ...

4.3.2 Outer circulation weight removal

It took a long time to find out why:

return result.stream().distinct().collect(Collectors.toList());

It's very comfortable to use a stream() and a distinct() here if you want to go heavy. The problem is.. Or is it slow!!! So it needs to be weighted manually because there are duplicate numbers in the array, such as:

[1,1,1,1,2,2,2,2],target=6

When judging the order, there will be several

[1,1,2,2]

Therefore, for duplicate numbers, skip processing, and in the first level for, skip over duplicate numbers:

for(int i=0;i<len-2;++i)
	if(i>0 && nums[i] == nums[i-1]) continue;

Second, in the second level for, repetitive skips are also skipped:

for(int j=i+1;j<len-2;++j)
	if(j > i+1 && nums[j] == nums[j-1]) continue;

In this case, for example, for the above (different 1 is alphabetically distinguished)

[1(a),1(b),1(c),1(d),2,2,2,2]

It starts with 1 at a and 1 at b, then goes to the second level of the loop, because at this point j=i+1, points to 1 at b, so it doesn't skip 1, it goes into a double-pointer cycle. The second time j points to 1 at c, it repeats, and j skips until J points to 2.2 and then ends, it goes to the I level of the loop, because 1 skips and I skips until I points to 2.

Yes, so much said, no HashSet, no stream, just two lines:

if(i>0 && nums[i] == nums[i-1]) continue;
if(j>i + 1 && nums[j] == nums[j-1]) continue;

4.4 Submit

Do your best.

5 Sources

github

Code cloud

Posted by pixelfish on Sat, 25 Jan 2020 21:57:39 -0800