LeetCode刷题笔记-15.三数之和(3sum)
问题描述
给你一个包含n个整数的数组nums,判断nums中是否存在三个元素a,b,c ,使得a + b + c = 0 ?请你找出所有和为0且不重复的三元组。
说明:
- 答案中不可以包含重复的三元组
0 <= nums.length <= 3000-105 <= nums[i] <= 105
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
示例 2:
输入:nums = []
输出:[]
示例 3:
输入:nums = [0]
输出:[]
题解
1.暴力解法
算法解析
使用3重循环,一次循环固定一个数,通过3次循环找到解:
- 第一层循环固定下标为
i的数,i递增直到n-1 - 第二层循环固定下标为
j = i+1的数,j递增直到n-1 - 第三层循环固定下标为
k = j+1的数,k递增直到n-1
同时还可以通过将数组排序,避免最后的解集中出现组合一样但是顺序不同的解.
复杂度分析
- 时间复杂度: \(O(N^3)\), 使用3重循环,其时间复杂度为\(O(N^3)\)
- 空间复杂度: \(O(1)\), 只需要固定的额外空间
代码实现
- Python版
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
if n < 3:
return []
res = set() # 防止解重复
nums.sort() # 防止出现值次序不同但值组合相同的解
for i in range(n):
for j in range(i+1, n):
for k in range(j+1, n):
if nums[i] + nums[j] + nums[k] == 0:
res.add(f"{nums[i]}_{nums[j]}_{nums[k]}")
# f字符串前缀表达式为Python3.6及之后的特性
return [list(map(int, s.split('_'))) for s in res]
2.双指针解法
算法解析
该解法同上面暴力解法类似,同样的是固定一个数nums[i]然后求剩余两个数之和.只不过在第二层循环和第三层循环时,利用数组已经排序好的特点,采用双指针代替第二层循环和第三层循环,也就是说通过一个左指针left从前往后移动和一个右指针right从后往前移动找到满足题解条件的剩余的两个数.同样的需要先对数组排序.具体细节如下:
- 当
nums[len-1] < 0时,由于数组为递增数组,则在最后一个元素都小于0情况下不可能再有解,直接返回结果 - 当
nums[i] > 0时,由于数组为递增数组,则i之后的不可能有三数之和小于0,直接返回结果 - 当
nums[i] == nums[i-1](i > 0)时,即有重复的元素时,跳过重复元素. - 当令
left = i+1且right = n-1,当left < right时移动指针规则如下:- 当
nums[left] + nums[right] + nums[i] > 0时,则nums[right]太大,向前移动right指针 - 当
nums[left] + nums[right] + nums[i] < 0时,则nums[right]太小,向后移动left指针 - 当
nums[left] + nums[right] + nums[i] == 0时,则找到一个解,记录该解,并将左指针向后,右
指针向后移动寻找下一解,同时还应该跳过nums[left] == nums[left-1]或者nums[right] == nums[right+1]这样的重复元素的情况
- 当
本题难点在于如何避免重复解,通过排序和双指针法便可轻松的解决这一问题,这一解法还可推广到nSum问题(n>=3),可通过递归解决.
参考文章及资料
复杂度分析
- 时间复杂度: \(O(N^2)\),一般排序算法时间复杂度为\(O(NlogN)\),而双指针遍历和和最外层循环的时间复杂度为\(O(N^2)\)
- 空间复杂度: \(O(logN)\)或者\(O(1)\), 一般排序算法空间复杂度为\(O(logN)\)而对于本题核心算法来说其空间复杂度为\(O(1)\)
代码实现
- Java版
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
int len = nums.length;
List<List<Integer>> res = new ArrayList<List<Integer>>();
if (len < 3) return res; // 特殊情况
Arrays.sort(nums); // 排序便于去重
// 特性情况,数组元素递增,当nums[len-1] < 0时,不可能有解
if (nums[len-1] < 0) return res;
for (int i = 0; i < len; ++i) { // 固定一个数, 寻找剩余两个数
// 剪枝,数组元素递增,当nums[i] > 0时,nums[i]及之后不可能再有解
if (nums[i] > 0) return res;
// 若存在和之前相同数则应该跳过,防止重复解
if (i > 0 && nums[i] == nums[i-1]) continue;
int left = i+1;
int right = len-1;
while (left < right) {
int tmpSum = nums[left] + nums[right] + nums[i];
if (tmpSum > 0) {
--right; // 此时nums[right]值太大,应向前移动
} else if (tmpSum < 0) {
++left; // 此时nums[left]值太小,应向后移动
} else { // 此时三数之和等于0,记录该解
List<Integer> tmp = new ArrayList<>(3);
tmp.add(nums[i]);
tmp.add(nums[left++]); // 向后移动,寻找下一解
tmp.add(nums[right--]); // 向前移动,寻找下一解
res.add(tmp);
// 跳过重复的解
while (left < right && nums[left] == nums[left-1]) ++left;
while (left < right && nums[right] == nums[right+1]) --right;
}
}
} // end for
return res;
}
}
- Python版
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
res = list()
if (n < 3):
return res
nums.sort()
if nums[n-1] < 0:
return res
for i in range(n):
if nums[i] > 0:
return res
if i > 0 and nums[i] == nums[i-1]:
continue
left, right = i+1, n-1
while left < right:
if nums[i] + nums[left] + nums[right] < 0:
left += 1
elif nums[i] + nums[left] + nums[right] > 0:
right -= 1
else:
res.append([nums[i], nums[left], nums[right]])
left += 1
right -= 1
while left < right and nums[left] == nums[left-1]:
left += 1
while left < right and nums[right] == nums[right+1]:
right -= 1
return res
解题误区及心得总结
误区
- 对于双指针的理解不深入
- 对于排序在算法中的应用有欠缺
心得总结
- 双指针在序列中,特别时元素有特性的序列中求解很合适,甚至可以达到降维的目的
- 排序与去重有着很强的联系,通过先排序可以快速的去重.

浙公网安备 33010602011771号