天天AC系列(二):最接近的三數之和

1 題目

leetcode第16題,給定一個數組與一個目標數,找出數組中其中的三個數,這三個數的和要與目標數最接近。
在這裏插入圖片描述html

2 暴力

按慣例先來一次O(n3)的暴力:java

int temp = nums[0]+nums[1]+nums[2];
for(int i=0;i<nums.length;++i)
    for(int j=i+1;j<nums.length;++j)
        for(int k=j+1;k<nums.length;++k)
        {
            int temp1 = nums[i]+nums[j]+nums[k];
            if(Math.abs(temp-target) > Math.abs(temp1-target))
            {
                temp = temp1;
                if(temp == target)
                    return target;
            }
        }
return temp;

而後。。。。
在這裏插入圖片描述
受寵若驚啊,直接暴力竟然給過了。。。git

3 O(n2)

算了,這種暴力筆者本身也看不下去,搞點正經事,暴力的話直接三個循環,每一次都加三個數並判斷與target的距離,若是是target直接返回,若是不是則繼續,可是...O(n3)啊...
其實這也能夠用筆者上一篇文章中提到的雙指針法,先對數組排序,而後固定一個數,再用兩個指針指向起始端與末端,而後不斷向中間逼近。github

Arrays.sort(nums);
int t1 = nums[0]+nums[1]+nums[2];
for(int i=0;i<nums.length-2;++i)
{
    int left = i+1;
    int right = nums.length-1;

    while(left < right)
    {
        int t2 = nums[i]+nums[left]+nums[right];
        if(t2 == target)
            return target;
        else if(t2 > target)
            --right;
        else 
            ++left;
        if(abs(t1-target) > abs(t2-target))
        {
            t1 = t2;
        }
    }
}
return t1;

首先將數組排序,nums[i]爲固定的數,left和right爲兩個兩個指針,根據計算的t2=nums[i]+nums[left]+nums[right]判斷與target關係,大於的話向左移動右指針,小於的話向右移動左指針,直到兩指針相遇。排序須要O(n log n),兩個循環須要O(n2),總的時間複雜度爲O(n2).
在這裏插入圖片描述算法

4 衝擊2ms

去看了一下第一的那個解答,2ms,確實是快,主要是手寫了快排,而後在for裏面的循環中用了最大最小剪枝。數組

4.1 手寫快排

去查了一下Arrays.sort()的算法,它是幾種算法的組合:
在這裏插入圖片描述
圖片來源spa

只有當數組的長度小於286大於等於47時,纔會調用快速排序,所以這裏直接手寫了一個快排,不管長度多少都直接使用快排。
(原理就很少說了,手寫快排仍是稍微有那麼一點難度的...).net

public void qs(int [] nums,int l,int r)
{
    if(l < r-1)
    {
        int t = l;
        int ll = l+1;
        int rr = r-1;
        int temp;
        while(true)
        {
            while(t < rr && nums[t] < nums[rr])
                --rr;
            if(t < rr)
            {
                temp = nums[rr];
                nums[rr] = nums[t];
                nums[t] = temp;
                t = rr--;
            }
            else
                break;
            while(ll < t && nums[ll] < nums[t])
                ++ll;
            if(ll < t)
            {
                temp = nums[ll];
                nums[ll] = nums[t];
                nums[t] = temp;
                t = ll++;
            }
            else
                break;
        }
        qs(nums,l,t);
        qs(nums,t+1,r);
    }
}

本來兩個while循環中的條件是3d

while(ll < rr && ...)

後來出了bug,調了一下,發現範圍不對,改爲了兩個while:指針

while(t < rr && ...)
while(ll < t && ...)

4.2 最大最小剪枝

最小剪枝就是計算固定的那個數,還有兩個最小的數之和,判斷與目標值的大小,若是這個最小值大於目標值,那麼,結果有多是這個最小值,可是,不多是其餘值,由於這個值最小了,並且大於目標值,再與其餘值相加的話只會離目標值更遠,所以判斷是最小值後能夠直接break.

最大剪枝也相似,計算最大的兩個數與固定的那個數之和,判斷與目標值的大小,若是小於目標值,則結果有多是這個最大值,不多是其餘值,判斷完後也是直接break.

int left = i+1;
int right = nums.length-1;
if(left < right)
{
    int min = nums[i] + nums[left] + nums[left+1];
    if(min > target)
    {
        if(abs(min - target) < abs(t1 - target))
            t1 = min;
        continue;
    }
}

int max = nums[i] + nums[right] + nums[right-1];
if(max < target)
{
    if(abs(max - target) < abs(t1 - target))
        t1 = max;
    continue;
}

4.3 噢...

在這裏插入圖片描述
一個字,開心。

5 源碼

github
碼雲

相關文章
相關標籤/搜索