每日一道 LeetCode (52):三數之和

每天 3 分鐘,走上算法的逆襲之路。

前文合集

每日一道 LeetCode 前文合集

代碼倉庫

GitHub: https://github.com/meteor1993/LeetCode

Gitee: https://gitee.com/inwsy/LeetCode

題目:三數之和

難度:中等

題目來源:https://leetcode-cn.com/problems/3sum/

給你一個包含 n 個整數的數組 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?請你找出所有滿足條件且不重複的三元組。

注意:答案中不可以包含重複的三元組。

示例:

給定數組 nums = [-1, 0, 1, 2, -1, -4],

滿足要求的三元組集合爲:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

解題思路

之前一段時間都在寫 JVM 和 Tomcat 的一系列相關的文章,刷力扣題就給空下了,今天開始接着撿起來做題。

首先這道題我第一個反應是套三個循環,肯定做得出來,時間複雜度是 O(n^3) ,放在力扣裏面,按照一貫的尿性結果肯定是超時,這個方案我現在都已經懶得寫了(對,我現在就是這麼飄),除了鍛鍊下寫代碼的能力,對於算法好像沒啥提升。

其他的方案又想不出來,沒辦法,只能去翻答案。

答案上的思路就很有意思了,第一件事兒就是先給數組排序,把這個數組變成一個有序數組。

三個數加起來結果要是 0 ,會有一種特殊情況就是 0 + 0 + 0 = 0

刨除掉上面這種情況,那麼肯定第一位是負數,後面第二位只能比第一位大,第三位要比第二位大(這不是顯而易見嘛)。

套上前面的特殊情況,那麼會有一個確定的關係 a <= b <= c

這個問題解到這裏,還是沒有跳脫出來三重循環,那麼時間複雜度依然還是 O(n^3) ,這肯定不行,還需要繼續優化。

目標放在把時間複雜度降低到 O(n^2) 上,那麼就是兩重循環要搞定所有的事兒,最外層的循環也就是找 a 的那個循環,這個肯定是無法避免的,能玩的滑頭也就只剩下第二重循環了,在找 b 和 c 的過程,想辦法找到內在的聯繫,在一次循環中,直接把 b 和 c 全都找出來。

如果我們固定了 a 和 b ,那麼只會有一個唯一的 c 來滿足 a + b + c = 0 。當第二重循環往後再循環一個 b1 的時候,這時如果存在 a + b1 + c1 = 0 ,那麼這個 c1 一定要比之前的 c 來的要小。

這樣我們可以在第二重循環裏面考慮一個雙指針的方案,一個數組從小到大排列,取 b 的時候我們從小到大取,取 c 的時候我們從大到小來取,這時,我們取 b 和 c 其實是並列在第二重裏面的,這樣我們強行把三重循環降成了兩重循環。

這裏的 b 和 c 可以抽象成兩個指針,左指針和右指針,左指針每像右移動一位,右指針向左有可能移動 n 位,這和數組的元素有關,最差情況下會達到移動 n 次,因此,時間複雜度也是 O(n) ,第一重循環還有一個 O(n) ,所以整體的時間複雜度會達到 O(n^2) 。

代碼:

public List<List<Integer>> threeSum(int[] nums) {
    int n = nums.length;
    Arrays.sort(nums);
    List<List<Integer>> ans = new ArrayList<>();
    // 最外層循環,迭代 a
    for (int a = 0; a < n; ++a) {
        // 由於元素不能重複,遇到相同的直接下次循環
        if (a > 0 && nums[a] == nums[a - 1]) {
            continue;
        }
        // c 對應的指針初始指向數組的最右端
        int c = n - 1;
        int target = -nums[a];
        // 迭代 b
        for (int b = a + 1; b < n; ++b) {
            // 一樣先判斷是否和上次迭代的元素是否相同
            if (b > a + 1 && nums[b] == nums[b - 1]) {
                continue;
            }
            // b 的位置在 c 的左側
            while (b < c && nums[b] + nums[c] > target) {
                --c;
            }
            // 如果出現了指針重合,就可以退出循環了,說明沒找到對應的 c
            if (b == c) {
                break;
            }
            // 如果出現了 b 和 c 加起來等於 -a ,說明我們找到的最後的結果
            if (nums[b] + nums[c] == target) {
                List<Integer> list = new ArrayList<>();
                list.add(nums[a]);
                list.add(nums[b]);
                list.add(nums[c]);
                ans.add(list);
            }
        }
    }
    return ans;
}

不知道各位還記不記得之前兩數之和的解法,當我們確定了 a 和 b 以後,最終尋找 c 的時候還可以把整個數組放在一個哈希表中,通過哈希表的特性來判斷當前 c 是否存在,如果不存在,則進入下一次循環。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章