1. 程式人生 > >Leetcode之15. 3Sum (medium)

Leetcode之15. 3Sum (medium)

二層 固定 寫法 tar || 循環 ont 大於 amp

15. 3Sum (medium)

描述

Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.

Note:

The solution set must not contain duplicate triplets.

Example:

Given array nums = [-1, 0, 1, 2, -1, -4],

A solution set is:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

分析

首先是最容易想到的暴力破解,通過三重遍歷數組nums,依次確定nums[i]nums[j]nums[k]並計算三元素之和是否為0。這是最粗暴的解法,但是存在去重的問題,如[-1, 0, 1]和[0, 1, -1]這種情況。

其次,這個題目作為2Sum的進階題目,很容易聯想到將3Sum轉化為求target值為0 - nums[i],並在數組剩余元素中找出兩個元素之和為target的2Sum問題。但是同樣存在去重問題。

關於去重,由於是一個List

最後,以上兩種思路都存在去重問題,問題需要的是找出數組中三個元素之和為0的所有組合。去重過程很明顯是和結果無關,但是卻非常麻煩,因此要優化算法就要著眼於移除去重這個步驟。

在暴力破解的時候就該意識到邊界問題。

在做第一層循環時可以這樣寫:for(int i = 0; i < nums.length - 2; i++)。即第一層循環的結束條件是nums.length - 2,並不需要到nums.length

同樣第二層循環時:for(int j = i + 1; j < nums.length - 1; j++)。開始索引不需要從0開始,可以直接從i + 1開始,而結束為nums.length - 1

第三層:for(int k = j + 1; k < nums.length; k++)

可以看到在暴力破解的時候,我們已經有意識地通過邊界條件過濾掉一些情況,進行了初步優化。註意到數組本身是無序的,所以在確定元素的時候難以界定當前遍歷元素是否已經被選中過。如果數組是有序的,那麽三重遍歷的時候就可以有意識地跳過重復元素。到這裏已經對暴力破解的解題思路進行了優化,但是三重遍歷無疑是導致時間復雜度為O(N^3),這麽高的時間復雜度肯定是要被拋棄的。那麽該如何繼續優化呢?

嘗試優化思路二。首先使用排序解決去重問題。遍歷排序後的數組,固定第一個元素為nums[i],接下來在索引位i + 1nums.length之間找出兩個元素nums[j]nums[k],二者之和為0 - nums[i]。固然這可以做遍歷兩次達到目的,相信基本上2Sum都是這樣完成的。但是針對一個有序數組,夾逼法可以將這個過程的時間復雜度降為O(N)。因此,使用夾逼法找出剩余兩個元素。ps,別忘了同時對2Sum使用夾逼法進行優化。

代碼

public List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> result = new LinkedList<List<Integer>>();
    if (nums == null || nums.length < 3) {
        return result;
    }
    // 對數組排序
    Arrays.sort(nums);
    //固定第一個元素nums[i]
    for (int i = 0; i < nums.length - 2; i++) {
        //默認是從小到大的排序,所以當nums[i]大於0的時候,就可以結束
        if (nums[i] > 0) {
            break;
        }
        //nums[i - 1] != nums[i]執行了去重,註意這裏在理解的時候要意識到此時操作的數組已經是有序數組
        if (i == 0 || nums[i - 1] != nums[i]) {
            //使用加逼法
            int j = i + 1;
            int k = nums.length - 1;
            while (j < k) {
                int sum = nums[i] + nums[j] + nums[k];
                if (sum == 0) {
                    result.add(Arrays.asList(nums[i], nums[j], nums[k]));
                } 
                if (sum <= 0) {
                    while (j < k && nums[j] == nums[++j]);
                }
                if(sum >= 0){
                    while (j < k && nums[k] == nums[--k]);
                }
            }
        }
    }
    return result;
}

上面的代碼是優化之後的代碼,對於理解加逼的過程有點不便,下面是加逼的原始寫法:

while (j < k) {
    int target = 0 - nums[i];
    if(target == (nums[j] + nums[k])){
        result.add(Arrays.asList(nums[i], nums[j], nums[k]));
        j++;
        while(nums[j] == nums[j - 1] && j < k){ //去重,註意這是一個有序數組
            j++;
        }
        k--;
        while(nums[k] == nums[k + 1] && j < k){ //去重,註意這是一個有序數組
            k--;
        }
    }else if(target < (nums[j] + nums[k])){
        k--;
        while(nums[k] == nums[k + 1] && j < k){ 
            k--;
        }
    }else if(target > (nums[j] + nums[k])){
        j++;
        while(nums[j] == nums[j - 1] && j < k){ 
            j++;
        }
    }  
}

Leetcode之15. 3Sum (medium)