|
| 1 | +# LeetCode 第 15 号问题:三数之和 |
| 2 | + |
| 3 | +> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。 |
| 4 | +> |
| 5 | +> 同步博客:https://www.algomooc.com |
| 6 | + |
| 7 | +题目来源于 LeetCode 上第 15 号问题:三数之和。 |
| 8 | + |
| 9 | +### 题目描述 |
| 10 | + |
| 11 | +给定一个包含 *n* 个整数的数组 `nums`,判断 `nums` 中是否存在三个元素 *a,b,c ,*使得 *a + b + c =* 0 ?找出所有满足条件且不重复的三元组。 |
| 12 | + |
| 13 | +**注意:**答案中不可以包含重复的三元组。 |
| 14 | + |
| 15 | +#### 示例 |
| 16 | + |
| 17 | +``` |
| 18 | +给定数组 nums = [-1, 0, 1, 2, -1, -4], |
| 19 | + |
| 20 | +满足要求的三元组集合为: |
| 21 | +[ |
| 22 | + [-1, 0, 1], |
| 23 | + [-1, -1, 2] |
| 24 | +] |
| 25 | +``` |
| 26 | + |
| 27 | +### 题目解析 |
| 28 | + |
| 29 | +最容易想到的就是三重循环暴力法搜索,时间复杂度为 `O(n^3)`. 有点高啊,优化一下. |
| 30 | + |
| 31 | +通过题目我们了解到,主要问题在于 `搜索所有满足条件的情况` 和 `避免重复项`,那么我们可以使用 `升序数组 + 双指针` 有效处理问题并降低时间复杂度. |
| 32 | + |
| 33 | +你可能想知道为啥会选择使用这个方案 ? |
| 34 | + |
| 35 | +首先数组排序时间复杂度可以达到 `O(NlogN)`,这点时间消耗我们是能接受的,另外根据有序数组的特性,数组重复项会挨在一起,不需要额外的空间存储就能跳过重复项,由于是升序,当发现最左边的数值大于0,就可以及时跳出来结束运算. |
| 36 | + |
| 37 | +双指针可以用来`降维`. 通过遍历数组,取当前下标值为`定值`,双指针代表`定值`后面子数组的`首尾数值`,通过不断靠近双指针来判断三个值的和。 |
| 38 | + |
| 39 | +具体算法流程如下: |
| 40 | + |
| 41 | +1. 特判:对于数组长度 `n`,如果数组为 `null` 或者数组长度小于 `3`,返回`[ ]` ; |
| 42 | +2. 数组升序排序; |
| 43 | +3. 遍历数组: |
| 44 | + - 若 `num[i] > 0`:因为是升序,所以结果不可能等于0,直接返回结果; |
| 45 | + - 令左指针 `L = i + 1`,右指针 `R = n - 1`,当 `L < R` 时,执行循环: |
| 46 | + - 当 `nums[i] + nums[L] + nums[R] == 0` ,执行循环,判断左指针和右指针是否和下一位置重复,`去除重复解`。并同时将 `L,R` 移到下一位置,寻找新的解; |
| 47 | + - 若`和`大于 `0`,说明 `nums[R]` 太大,`R指针` 左移 |
| 48 | + - 若`和`小于 `0`,说明 `nums[L]` 太小,`L指针` 右移 |
| 49 | + |
| 50 | +### 动画描述 |
| 51 | + |
| 52 | + |
| 53 | + |
| 54 | +### 参考代码 |
| 55 | + |
| 56 | +```javascript |
| 57 | + // lang = JavaScript |
| 58 | +var threeSum = function(nums) { |
| 59 | + let res = []; |
| 60 | + if (nums == null || nums.length < 3) { |
| 61 | + return res; |
| 62 | + } |
| 63 | + const len = nums.length; |
| 64 | + nums.sort((a, b) => a - b); // 升序 |
| 65 | + for (let i = 0; i < len - 2;) { |
| 66 | + const element = nums[i]; |
| 67 | + if (element > 0) { |
| 68 | + // 如果当前数字大于0,则三数之和一定大于0,所以结束循环 |
| 69 | + break; |
| 70 | + } |
| 71 | + let L = i + 1, |
| 72 | + R = len - 1; |
| 73 | + while (L < R) { |
| 74 | + const sum = element + nums[L] + nums[R]; |
| 75 | + if (sum == 0) { |
| 76 | + res.push([element, nums[L], nums[R]]); |
| 77 | + // 左右指针去重 & L+1 & R-1 |
| 78 | + while (L < R && nums[L] == nums[++L]); |
| 79 | + while (L < R && nums[R] == nums[--R]); |
| 80 | + }else if (sum < 0) { |
| 81 | + while (L < R && nums[L] == nums[++L]); |
| 82 | + }else { |
| 83 | + while (L < R && nums[R] == nums[--R]); |
| 84 | + } |
| 85 | + } |
| 86 | + // 定值去重 |
| 87 | + while (nums[i] == nums[++i]); |
| 88 | + } |
| 89 | + return res; |
| 90 | +}; |
| 91 | +``` |
| 92 | + |
| 93 | +### 复杂度分析 |
| 94 | + |
| 95 | +- 时间复杂度:`O(n^2)` |
| 96 | + |
| 97 | + 数组排序 `O(NlogN)`, 遍历数组`O(n)`, 双指针遍历 `O(n)`, 总体复杂度为 `O(NlogN) + O(n) * O(n)` ,`O(n^2)` |
| 98 | + |
| 99 | +- 空间复杂度:`O(1)` |
| 100 | + |
| 101 | + |
0 commit comments