算法-----三數之和等於0


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

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

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

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

我的解答:

第一版:

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        if (nums == null || nums.length == 0) {
            return result;
        }
        boolean needAdd = true;
        boolean hasZore = false;
        int length = nums.length;
        for (int i = 0; i < length; i++) {
            for (int j = i +1; j <length; j++){
                for (int k = j + 1; k < length ; k++ ){
                    if (nums[i] + nums[j] + nums[k] == 0) {

//                        long l = System.nanoTime();
                        needAdd = true;
                        if (result.size() == 0) {
                            List<Integer> integers = Arrays.asList(nums[i], nums[j], nums[k]);
//                            integers.sort((o1, o2) -> o1 > o2 ? -1 : 1);
                            if (nums[i] ==nums[j] &&nums[j] ==  nums[k]) {
                                hasZore = true;
                            }
                            result.add(integers);
                        }else {
                            for (List list : result) {
                                if (list.contains(nums[i]) && list.contains(nums[j]) &&list.contains(nums[k])
                                        ) {
//                                    list.retainAll()
                                    needAdd = false;
                                    if (nums[i] ==nums[j] && nums[j]==  nums[k] && !hasZore) {
                                        needAdd = true;
                                    }
                                }
                            }
                            if (needAdd) {
                                if (nums[i] ==nums[j] &&nums[j] ==  nums[k]) {
                                    hasZore = true;
                                }
                                result.add(Arrays.asList(nums[i], nums[j], nums[k]));
                            }

                        }
//                        System.out.println(System.nanoTime() - l);
                    }
                }
            }
        }
        return result;
    }
}

寫的超級復雜,超級垃圾,而且是O(n^3)的算法,3000個數據的時候,執行時間超過了leeCode 的最低標准。

第二版

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
         List<List<Integer>> result = new ArrayList<>();
        if (nums == null || nums.length == 0) {
            return result;
        }
        int length = nums.length;
        MaoPao(nums);


                int numj,numk;
        for (int i = 0; i< length; i++){
            for (int j = i+1, k = length -1; j<k && j< length && k<length;){
                if (nums[i] + nums[j] + nums[k] == 0) {
                    List<Integer> integerList = Arrays.asList(nums[i], nums[j], nums[k]);
                    if (!result.contains(integerList)) {
                        result.add(integerList);
                    }
                    j++;
                    k--;
                }else if (nums[i] + nums[j] + nums[k] >0){
                    numk = nums[k];
                    do {
                        k--;
                    }while (nums[k] >=numk && k>i);
                }else {
                    numj = nums[j];
                    do {
                        j++;
                    }while (nums[j] <=numj && j<k);
                }
            }
        }
        return result;
    }
    
        public  void MaoPao(int nums[]) {
        if (nums ==null) {
            return;
        }
        int len = nums.length;
        int temp;
        for (int i = 0; i < len; i++) {
            for (int j = i + 1; j < len; j++) {
                if (nums[i] > nums[j]) {
                    temp = nums[j];
                    nums[j] = nums[i];
                    nums[i] = temp;
                }
            }
        }
    }
}

采取了別人的建議,算法復雜度O(n^2)勉強通過。首先排序,然后一個循環取i, 一個循環直接取兩個數,簡化了算法。里面還有一些細節優化,是我寫算法學到的。后來我百度了才知道,快排比冒泡快,而且jdk 默認的Arrays.sort 方法 更快。是別人寫的一個算法。突然覺得,自己弱爆了。這個三數之和的算法,就把自己難倒了。別說寫的算法成為jdk 源碼了。

網上最快的算法:

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        if (nums.length < 3)
                return Collections.emptyList();
            List<List<Integer>> res = new ArrayList<>();
            int minValue = Integer.MAX_VALUE;
            int maxValue = Integer.MIN_VALUE;
            int negSize = 0;
            int posSize = 0;
            int zeroSize = 0;
            for (int v : nums) {
                if (v < minValue)
                    minValue = v;
                if (v > maxValue)
                    maxValue = v;
                if (v > 0)
                    posSize++;
                else if (v < 0)
                    negSize++;
                else
                    zeroSize++;
            }
            if (zeroSize >= 3)
                res.add(Arrays.asList(0, 0, 0));
            if (negSize == 0 || posSize == 0)
                return res;
            if (minValue * 2 + maxValue > 0)
                maxValue = -minValue * 2;
            else if (maxValue * 2 + minValue < 0)
                minValue = -maxValue * 2;

            int[] map = new int[maxValue - minValue + 1];
            int[] negs = new int[negSize];
            int[] poses = new int[posSize];
            negSize = 0;
            posSize = 0;
            for (int v : nums) {
                if (v >= minValue && v <= maxValue) {
                    if (map[v - minValue]++ == 0) {
                        if (v > 0)
                            poses[posSize++] = v;
                        else if (v < 0)
                            negs[negSize++] = v;
                    }
                }
            }
            Arrays.sort(poses, 0, posSize);
            Arrays.sort(negs, 0, negSize);
            int basej = 0;
            for (int i = negSize - 1; i >= 0; i--) {
                int nv = negs[i];
                int minp = (-nv) >>> 1;
                while (basej < posSize && poses[basej] < minp)
                    basej++;
                for (int j = basej; j < posSize; j++) {
                    int pv = poses[j];
                    int cv = 0 - nv - pv;
                    if (cv >= nv && cv <= pv) {
                        if (cv == nv) {
                            if (map[nv - minValue] > 1)
                                res.add(Arrays.asList(nv, nv, pv));
                        } else if (cv == pv) {
                            if (map[pv - minValue] > 1)
                                res.add(Arrays.asList(nv, pv, pv));
                        } else {
                            if (map[cv - minValue] > 0)
                                res.add(Arrays.asList(nv, cv, pv));
                        }
                    } else if (cv < nv)
                        break;
                }
            }
            return res;
        
    }
}

這個算法,我看了一遍竟然沒有看懂。后來邊調試邊看,才懂了作者的意圖。
大概記錄一下;

  1. 首先遍歷一下數組,拿到最大和最小值。以及正數的個數和負數的個數以及0的個數。
  2. 如果有三個0,那么直接把三個0放到結果的list 里面。
  3. 如果正數或者負數集合里面,有一個集合是空的,說明不可能再有結果。
  4. 走到這一步,說明,數組里面有正數有負數。那么,最小值的2倍 (一定是負的,因為最小值一定是負責)+ 最大值大於0,說明數組中存在的最大值沒有用,因為沒有可能和其他任何兩個數加起來等於0.這時候,理論上的最大值邊界是-minValue * 2。只有在這個區間的數,才有可能加起來等於0.同理,算出理論上的最小值。
  5. 用了三個數組,一個正數數組,一個負數數組,一個map 用來記錄比最小值大n 的數是否有。
  6. 排序數組
  7. 循環負數的數組,得到一個值,然后除以2,得到一個中間值,然后找到比這個數中間值大的位置。從這個位置往后遍歷。
  8. 然后算出0 減去這兩個數的差值是多少。如果這個差值正好等於當前的正數或者負數,那么看下這個正數或者負數的個數是不是>1 個,如果是,說明存在多個該值,那么就把該組數據放到結果里面。如果都不是,那么去看下這個差值在數組里面是否存在,如果存在返回該組合,不存在不管。

我的算法1000多毫秒,最快的是30毫秒,將近30多倍的效率。

我佛慈悲,當以不恥下問,學無止境 。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM