Leecode#15刷题之三数之和

题目

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

**注意:**答案中不可以包含重复的三元组。

示例 1:

复制代码
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

复制代码
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

复制代码
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

提示:

  • 3 <= nums.length <= 3000
  • -105 <= nums[i] <= 105

Java语言

java 复制代码
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(nums);

        for (int i = 0; i < nums.length - 2; i++) {
            // 跳过重复的第一个数
            if (i > 0 && nums[i] == nums[i - 1]) continue;

            // 剪枝:最小的三个数都 > 0,不可能和为 0
            if (nums[i] + nums[i + 1] + nums[i + 2] > 0) break;
            // 剪枝:当前数 + 最大的两个数都 < 0,跳过当前数
            if (nums[i] + nums[nums.length - 2] + nums[nums.length - 1] < 0) continue;

            int left = i + 1, right = nums.length - 1;
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];

                if (sum < 0) {
                    left++;
                } else if (sum > 0) {
                    right--;
                } else {
                    result.add(Arrays.asList(nums[i], nums[left], nums[right]));

                    // 跳过重复的 left
                    while (left < right && nums[left] == nums[left + 1]) left++;
                    // 跳过重复的 right
                    while (left < right && nums[right] == nums[right - 1]) right--;

                    left++;
                    right--;
                }
            }
        }

        return result;
    }
}

核心思路: 排序 + 双指针。先排序,固定一个数 nums[i],然后用双指针在剩余部分找两数之和等于 -nums[i]


逐行说明:

java 复制代码
List<List<Integer>> result = new ArrayList<>();

Arrays.sort(nums);

创建结果列表。先排序是双指针的前提,排序后重复值相邻,也方便去重和剪枝。

java 复制代码
for (int i = 0; i < nums.length - 2; i++) {

遍历第一个数,到 length - 2 停止(至少留两个位置给双指针)。

java 复制代码
if (i > 0 && nums[i] == nums[i - 1]) continue;

去重 :如果当前数和前一个相同,跳过,避免产生重复三元组。例如 [-1,-1,0,1],第一个 -1 找了 [-1,0,1],第二个 -1 跳过。

java 复制代码
if (nums[i] + nums[i + 1] + nums[i + 2] > 0) break;

if (nums[i] + nums[nums.length - 2] + nums[nums.length - 1] < 0) continue;

剪枝优化

  • 最小的三个数之和都 > 0,后面不可能有更小的了,直接 break
  • 当前数加上最大的两个数都 < 0,当前数太小,continue 换下一个
java 复制代码
int left = i + 1, right = nums.length - 1;

while (left < right) {

int sum = nums[i] + nums[left] + nums[right];

双指针,lefti+1 开始,right 从末尾开始。计算三数之和。

java 复制代码
if (sum < 0) {

left++;

} else if (sum > 0) {

right--;

} else {

result.add(Arrays.asList(nums[i], nums[left], nums[right]));
  • 和 < 0:太小了,left 右移让和变大
  • 和 > 0:太大了,right 左移让和变小
  • 和 == 0:找到一组,加入结果
java 复制代码
while (left < right && nums[left] == nums[left + 1]) left++;

while (left < right && nums[right] == nums[right - 1]) right--;

left++;

right--;

去重 :跳过与当前 left/right 相同的值,然后各移动一步继续找下一组。


示例 1 演示: nums = [-1,0,1,2,-1,-4] → 排序后 [-4,-1,-1,0,1,2]

i numsi left right sum 操作 result
0 -4 1 5 -4+(-1)+2=-3 <0, left++ \[\]
-4 2 5 -4+(-1)+2=-3 <0, left++ \[\]
-4 3 5 -4+0+2=-2 <0, left++ \[\]
-4 4 5 -4+1+2=-1 <0, left++ → left>=right 退出 \[\]
1 -1 2 5 -1+(-1)+2=0 找到 \[-1,-1,2]
-1 3 4 -1+0+1=0 找到 \[-1,-1,2,-1,0,1]
2 -1 (与i=1相同,skip) continue
3 0 4 5 0+1+2=3 >0, right-- → 退出

最终返回 [[-1,-1,2],[-1,0,1]]

时间复杂度 O(n²),空间复杂度 O(1)(不计排序)。

python语言

python 复制代码
class Solution(object):
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        result = []
        nums.sort()

        for i in range(len(nums) - 2):
            # 跳过重复的第一个数
            if i > 0 and nums[i] == nums[i - 1]:
                continue

            # 剪枝:最小的三个数都 > 0,不可能和为 0
            if nums[i] + nums[i + 1] + nums[i + 2] > 0:
                break
            # 剪枝:当前数 + 最大的两个数都 < 0,跳过当前数
            if nums[i] + nums[-2] + nums[-1] < 0:
                continue

            left, right = i + 1, len(nums) - 1
            while left < right:
                total = nums[i] + nums[left] + nums[right]

                if total < 0:
                    left += 1
                elif total > 0:
                    right -= 1
                else:
                    result.append([nums[i], nums[left], nums[right]])

                    # 跳过重复的 left
                    while left < right and nums[left] == nums[left + 1]:
                        left += 1
                    # 跳过重复的 right
                    while left < right and nums[right] == nums[right - 1]:
                        right -= 1

                    left += 1
                    right -= 1

        return result

逻辑与 Java 版完全一致,逐行说明:

python 复制代码
result = []

nums.sort()

创建结果列表,nums.sort() 原地排序。对应 Java 的 Arrays.sort(nums)

python 复制代码
for i in range(len(nums) - 2):

if i > 0 and nums[i] == nums[i - 1]:

continue

遍历第一个数。nums[i] == nums[i - 1] 跳过重复值,对应 Java 的 nums[i] == nums[i - 1]

python 复制代码
if nums[i] + nums[i + 1] + nums[i + 2] > 0:

break

if nums[i] + nums[-2] + nums[-1] < 0:

continue

剪枝优化。nums[-2]nums[-1] 是 Python 的负索引,直接取倒数第二和最后一个元素,比 Java 的 nums[nums.length - 2] 简洁很多。

python 复制代码
left, right = i + 1, len(nums) - 1

while left < right:

total = nums[i] + nums[left] + nums[right]

双指针。left, right = i + 1, len(nums) - 1 一行同时赋值两个变量。

python 复制代码
if total < 0:

left += 1

elif total > 0:

right -= 1

else:

result.append([nums[i], nums[left], nums[right]])
  • 和 < 0:left 右移
  • 和 > 0:right 左移
  • 和 == 0:result.append([a, b, c]) 添加列表,对应 Java 的 Arrays.asList(a, b, c)
python 复制代码
while left < right and nums[left] == nums[left + 1]:

left += 1

while left < right and nums[right] == nums[right - 1]:

right -= 1

left += 1

right -= 1

跳过重复值后,各移动一步继续找。+= 1 / -= 1 对应 Java 的 ++ / --


与 Java 版的关键区别:

特性 Java Python
排序 Arrays.sort(nums) nums.sort()
列表长度 nums.length len(nums)
添加元素 result.add(Arrays.asList(...)) result.append([...])
负索引 nums[nums.length - 1] nums[-1]
双变量赋值 int left = i+1, right = n-1; left, right = i+1, n-1
自增/自减 left++ / right-- left += 1 / right -= 1

时间复杂度 O(n²),空间复杂度 O(1)(不计排序)。

相关推荐
倒头就睡的小比特17 小时前
算法竞赛C++常用的STL
c++·算法
小羊没烦恼!17 小时前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
猎头南楼18 小时前
知识社区推荐系统实践:新用户冷启动与长短期兴趣建模的挑战 资深推荐算法工程师
人工智能·深度学习·算法·机器学习
旖旎夜光19 小时前
力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解
c++·学习·算法·leetcode·力控
wzdark19 小时前
大规模并行计算中的负载均衡算法研究4
算法
Because_of_Her119 小时前
并查集-听课笔记
笔记·算法·并查集
码流子19 小时前
高速公路安全监测实践:碰撞监测预警+物联网底座,从感知到处置的闭环
大数据·人工智能·物联网·算法·架构
another heaven20 小时前
【算法/C++ MD5算法能否逆解码?原理、C++实现与同类哈希算法对比】
c++·算法·哈希算法
wzdark20 小时前
从算法设计模式看编程思维的抽象能力4
算法
2601_9622186120 小时前
万象生鲜系统称重自动多退少补算法解决生鲜非标品痛点
大数据·数据库·人工智能·python·算法