天天AC系列(四):四數之和

1 題目

Leetcode第18題,給定一個數組與一個target,找出數組中的四個數之和爲target的不重複的全部四個數. 在這裏插入圖片描述java

2 暴力

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;

判斷長度,而後排序,直接上四個for,而後... 在這裏插入圖片描述 好! 慘敗.git

3 優化

3.1 去掉結果排序

首先最後的排序是沒必要要的,也就是後面的github

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;
});

對結果進行排序沒必要要,雖然會在測試時與答案有差異,可是提交的話不須要排序.算法

3.2 stream去重

以前的操做用的是HashSet進行去重,有一個符合的四元組就直接添加進集合中,如今採用了stream+distinct去重:數組

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

3.3 雙指針+最大最小剪枝

能夠利用相似三數之和的思想,固定一個數,雙指針分別指向兩端的兩個數,這裏的話,四個數,選擇固定兩個數,計算它們的和並把它們看做一個數,便可利用雙指針.測試

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爲固定的數,left與right就是雙指針,根據"三數"之和判斷與目標值的大小移動雙指針. 最小剪枝就是首先計算"三數"的最小值,若大於目標值就能夠跳過,最大剪枝就是計算"三數"的最大值,若小於目標值則跳過,進入下一個循環:優化

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 提交

在這裏插入圖片描述

呃...好了那麼一點點吧.指針

4 來來來再快一點

4.1 初始判斷

首先,初始的判斷能夠再簡單一點,若是數組爲空或長度小於4,直接返回.code

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

4.2 一次不夠,就再剪幾回

上面的算法中,只是在兩層for裏面進行了一次最大最小剪枝,能夠在沒進入for以前剪一次:blog

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)

注意要先排序,而後直接判斷整個數組的最大最小值並與target判斷. 而後在進入第一層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)
}

由於數組是升序排序的,所以,"最左邊"四個數確定是最小值,若這個最小值大於target,能夠直接break了,可是,最右邊三個數與nums[i]相加不必定爲最大值,所以判斷以後若小於target只能continue.

4.3 去重

4.3.1 雙指針去重

首先,在雙指針的循環中,若發現了有四個數符合條件,能夠嘗試屢次移動指針:

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;

由於值同樣的能夠一次性移動指針,不須要再次進行和的判斷. 呃,能夠嘗試提交了.

在這裏插入圖片描述

咦,不對啊,作了這麼多,沒快多少啊... 爲啥呢... ...

4.3.2 外循環去重

找了好久,發現是這裏的緣由:

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

這裏去重的話,用是用的很舒服,一個stream(),一個distinct()就行了,問題是...仍是很慢啊!!! 因此呢,須要手動去重,出現重複的緣由就是數組中有重複的數,好比:

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

順序判斷時,會好幾個

[1,1,2,2]

所以,對於重複的數,進行跳過處理,在第一層for中,對重複過的進行跳過:

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

其次,在第二層for中,也對重複過的進行跳過:

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

這樣的話,例如對於上面的(不一樣的1用字母區分)

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

一開始是a處的1與b處的1,而後到了第二層循環,由於此時j=i+1,指向b處的1,所以不會跳過1,會進入雙指針循環,第二次j指向c處的1,出現重複,j不斷跳過直到j指向2.而後2結束後,到了i這層循環,由於1出現過,i不斷跳過直到i指向2.

沒錯,說了這麼多,去重不須要什麼HashSet,不須要什麼stream,只需兩行:

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

4.4 提交

在這裏插入圖片描述

盡力了.

5 源碼

github

碼雲

相關文章
相關標籤/搜索