1. 程式人生 > >Leetcode演算法Java全解答--015. 三數之和

Leetcode演算法Java全解答--015. 三數之和

Leetcode演算法Java全解答–015. 三數之和

題目

給定一個包含 n 個整數的陣列 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?

找出所有滿足條件且不重複的三元組。

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

示例:

給定陣列 nums = [-1, 0, 1, 2, -1, -4],
滿足要求的三元組集合為:
[
    [-1, 0, 1],
    [-1, -1, 2]
]

想法

  1. 暴力破解:
  • 巢狀三層迴圈
    • 注意:遍歷結果是否相同
    • 時間複雜度比較高
  1. 先排序,以0位分割點,再進行計算
  • 找到負數最大位置、正數最小位置、0的個數
    • 0超過三個,則肯定有個答案是000
    • 當沒有負數或者沒有正數的時候,直接返回
  • 滑動列表的方法
    • 以負數們為迴圈(最大值為數最大位置)
      • 負數的絕對值為目標
      • 雙指標 left為下一個數,right為最大長度
      • 兩數相加小於目標,則right左移,否則left右移
      • 通過nums[leftIndex] == nums[leftIndex + 1] 控制重複資料
        • 注意這裡陣列越界,要加個left<right判斷

結果

超過99%的測試案例

時間複雜度:複雜度最高的應該就是排序演算法了Arrays.sort(nums)

空間複雜度:接收結果List<List> result

總結

  1. 想到了排序
  2. 沒有想到使用滑動列表
  3. 在書寫程式碼的時候,很多細節沒有考慮到,比如重複

程式碼

我的答案

   /*
 * Copyright (C), 2015-2018
 * FileName: Solution015
 * Author:   zhao
 * Date:     2018/11/9 19:45
 * Description: 15. 三數之和
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改時間           版本號              描述
 */
package com.lizhaoblog.mid;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * 〈一句話功能簡述〉<br>
 * 〈15. 三數之和〉
 *
 * @author zhao
 * @date 2018/11/9 19:45
 * @since 1.0.1
 */
public class Solution015 {

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

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

     例如, 給定陣列 nums = [-1, 0, 1, 2, -1, -4],
     滿足要求的三元組集合為:
     [
     [-1, 0, 1],
     [-1, -1, 2]
     ]
     **************************************/

    /**************************************
     *
     * 想法:
     *      1.暴力破解:
     *          巢狀三層迴圈
     *              注意:遍歷結果是否相同
     *              時間複雜度比較高
     *      2.先排序,以0位分割點,再進行計算
     *          找到負數最大位置、正數最小位置、0的個數
     *              0超過三個,則肯定有個答案是000
     *              當沒有負數或者沒有正數的時候,直接返回
     *          滑動列表的方法
     *              以負數們為迴圈(最大值為數最大位置)
     *                  負數的絕對值為目標
     *                  雙指標 left為下一個數,right為最大長度
     *                  兩數相加小於目標,則right左移,否則left右移
     *                  通過nums[leftIndex] == nums[leftIndex + 1] 控制重複資料
     *                      注意這裡陣列越界,要價格left<right判斷
     * 我的做法
     *      超過99%的測試案例
     *      時間複雜度:複雜度最高的應該就是排序演算法了Arrays.sort(nums)
     *      空間複雜度:接收結果List<List<Integer>> result
     * 程式碼執行過程:
     *          找到負數最大位置、正數最小位置、0的個數
     *              0超過三個,則肯定有個答案是000
     *              當沒有負數或者沒有正數的時候,直接返回
     *          滑動列表的方法
     *              以負數們為迴圈(最大值為數最大位置)
     *                  負數的絕對值為目標
     *                  雙指標 left為下一個數,right為最大長度
     *                  兩數相加小於目標,則right左移,否則left右移
     *                  通過nums[leftIndex] == nums[leftIndex + 1] 控制重複資料
     *                      注意這裡陣列越界,要價格left<right判斷
     * 總結:
     *      1. 想到了排序
     *      2. 沒有想到使用滑動列表
     *      3. 在書寫程式碼的時候,很多細節沒有考慮到,比如重複
     * ***********************************/
    public List<List<Integer>> threeSum(int[] nums) {
        if (nums == null || nums.length < 1) {
            return Collections.EMPTY_LIST;
        }

        Arrays.sort(nums);
        int target = 0;
        int targetCount = 0;
        // 負數最大索引
        int spilt0 = -1;
        // 正數最小索引
        int spilt2 = -1;

        for (int i = 0; i < nums.length; i++) {
            if (nums[i] < target) {
                spilt0 = i;
            } else if (nums[i] == target) {
                targetCount++;
            } else if (nums[i] > target) {
                spilt2 = i;
                break;
            }
        }

        // 出現超過三個0,就肯定有一個答案是000
        List<List<Integer>> result = new ArrayList<>();
        if (targetCount >= 3) {
            List<Integer> list = new ArrayList<>();
            list.add(0);
            list.add(0);
            list.add(0);
            result.add(list);
        }

        // 這種時候只有非負數,或者只有非正數,除了000,否則是湊不齊的
        if (spilt0 == -1 || spilt2 == -1) {
            // 這裡有可能全部都是0,不然就肯定無法滿足條件
            return result;
        }

        // 當整體資料有正有負,就遍歷負數就好
        // 然後使用滑動列表
        // 以負數的絕對值為目標,後面2數相加小於目標,則左移,否則右移
        for (int i = 0; i <= spilt0; i++) {
            // 如果和之前那個資料相同,則會是重複事件
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }

            int val = nums[i] * -1;
            int leftIndex = i + 1;
            int rightIndex = nums.length - 1;
            while (leftIndex < rightIndex) {
                int tmpVal = nums[leftIndex] + nums[rightIndex];
                if (tmpVal > val) {
                    rightIndex--;
                } else if (tmpVal < val) {
                    leftIndex++;
                } else {
                    List<Integer> list = new ArrayList<>();
                    list.add(nums[i]);
                    list.add(nums[leftIndex]);
                    list.add(nums[rightIndex]);
                    result.add(list);
                    // 用nums[leftIndex] == nums[leftIndex + 1]控制重複
                    while (leftIndex < rightIndex && nums[leftIndex] == nums[leftIndex + 1]) {
                        leftIndex++;
                    }
                    while (leftIndex < rightIndex && nums[rightIndex] == nums[rightIndex - 1]) {
                        rightIndex--;
                    }
                    leftIndex++;
                    rightIndex--;
                }
            }
        }
        return result;
    }
}

大佬們的答案

    /**************************************
     * 比我好的答案 better
     * ***********************************/
    public List<List<Integer>> better(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;
    }

測試用例


    @Test
    public void test015() {
        // 建立測試案例
        int[] arr1 = new int[] { -1, 0, 1, 2, -1, -4 };

        // 測試案例期望值
        List<List<Integer>> expResult1 = new ArrayList<>();
        List<Integer> list1 = new ArrayList<>();
        list1.add(-1);
        list1.add(0);
        list1.add(1);
        List<Integer> list2 = new ArrayList<>();
        list2.add(-1);
        list2.add(-1);
        list2.add(2);
        expResult1.add(list1);
        expResult1.add(list2);


        // 執行方法
        Solution015 solution015 = new Solution015();
        List<List<Integer>> result1 = solution015.threeSum(arr1);

        // 判斷期望值與實際值
        Assert.assertEquals(expResult1, result1);

    }

其他

程式碼託管碼雲地址:https://gitee.com/lizhaoandroid/LeetCodeAll.git

檢視其他內容可以點選專欄或者我的部落格哈:https://blog.csdn.net/cmqwan

“大佬們的答案” 標籤來自leetcode,侵權請聯絡我進行刪改

如有疑問請聯絡,聯絡方式:QQ3060507060