大数跨境
0
0

LeetCode 15.三数之和详细解析

LeetCode 15.三数之和详细解析 点点技术屋
2025-12-07
0
题目分析

LeetCode 15.三数之和是一道经典的数组操作题目,要求找出数组中所有和为0且不重复的三元组。题目难度为中等,但实际解决起来有不少细节需要注意。

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

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

示例:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

这道题的难点在于如何高效地找到所有符合条件的三元组,同时避免重复结果。如果使用暴力解法,三重循环的时间复杂度为O(n³),对于n=3000的输入规模来说会超时。因此我们需要更高效的算法。

解题思路

排序 + 双指针法

最优解法是使用排序 + 双指针的组合,将时间复杂度优化到O(n²)。基本思路如下:

  1. 先对数组进行排序,这样可以方便后续去重和使用双指针

  2. 固定第一个数nums[i],然后使用双指针在i+1到n-1范围内寻找另外两个数

  3. 双指针分别为left=i+1和right=n-1,通过调整指针位置来寻找和为-nums[i]的两个数

  4. 注意去重操作,避免出现重复的三元组

为什么排序很重要?排序后:

  • 相同的元素会相邻,便于去重操作

  • 可以通过比较三数之和与0的关系来移动指针

  • 可以提前终止不可能的搜索

算法步骤详解

  1. 排序数组:对输入数组进行排序,时间复杂度O(n log n)

  2. 遍历第一个数

    • 固定第一个数nums[i],如果nums[i] > 0,由于数组已排序,后面不可能有三数之和为0,可以直接返回结果

    • 如果nums[i]与前一个数相同,跳过以避免重复结果

  3. 双指针寻找另外两个数

    • 左指针left = i + 1,右指针right = n - 1

    • 计算三数之和sum = nums[i] + nums[left] + nums[right]

    • 如果sum > 0,右指针左移;如果sum < 0,左指针右移;如果sum == 0,记录结果

    • 找到符合条件的三元组后,需要跳过相同的元素以避免重复

  4. 去重操作

    • 对第一个数去重:如果nums[i] == nums[i-1],跳过

    • 对第二个数去重:找到符合条件的三元组后,如果nums[left] == nums[left+1],left++

    • 对第三个数去重:找到符合条件的三元组后,如果nums[right] == nums[right-1],right--

关键难点解析

  1. 去重逻辑:这是本题最容易出错的地方。需要注意的是,去重操作应该在找到一个有效三元组之后进行,而不是之前。否则可能会漏掉一些有效的三元组。

  2. 指针移动:找到有效三元组后,需要同时移动左右指针,并进行去重操作。

  3. 边界条件:需要注意数组越界问题,特别是在去重操作时。

代码实现

Python实现

class Solution:    def threeSum(self, nums: List[int]) -> List[List[int]]:        # 对数组进行排序        nums.sort()        result = []        n = len(nums)
        # 遍历第一个数        for i in range(n - 2):            # 如果第一个数大于0,后面不可能有三数之和为0            if nums[i] > 0:                break
            # 去重:跳过相同的第一个数            if i > 0 and nums[i] == nums[i-1]:                continue
            # 双指针初始化            left, right = i + 1, n - 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]])
                    # 去重:跳过相同的第二个数                    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
        return result

Java实现

import java.util.ArrayList;import java.util.Arrays;import java.util.List;
class Solution {    public List<List<Integer>> threeSum(int[] nums) {        // 对数组进行排序        Arrays.sort(nums);        List<List<Integer>> result = new ArrayList<>();        int n = nums.length;
        // 遍历第一个数        for (int i = 0; i < n - 2; i++) {            // 如果第一个数大于0,后面不可能有三数之和为0            if (nums[i] > 0) {                break;            }
            // 去重:跳过相同的第一个数            if (i > 0 && nums[i] == nums[i - 1]) {                continue;            }
            // 双指针初始化            int left = i + 1, right = n - 1;
            while (left < right) {                int total = nums[i] + nums[left] + nums[right];
                if (total < 0) {                    left++;                } else if (total > 0) {                    right--;                } else {                    // 找到符合条件的三元组                    result.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    // 去重:跳过相同的第二个数                    while (left < right && nums[left] == nums[left + 1]) {                        left++;                    }                    // 去重:跳过相同的第三个数                    while (left < right && nums[right] == nums[right - 1]) {                        right--;                    }
                    // 移动双指针                    left++;                    right--;                }            }        }
        return result;    }}

Go实现

import "sort"
func threeSum(nums []int) [][]int {    // 对数组进行排序    sort.Ints(nums)    result := [][]int{}    n := len(nums)
    // 遍历第一个数    for i := 0; i < n - 2; i++ {        // 如果第一个数大于0,后面不可能有三数之和为0        if nums[i] > 0 {            break        }
        // 去重:跳过相同的第一个数        if i > 0 && nums[i] == nums[i-1] {            continue        }
        // 双指针初始化        left, right := i + 1, n - 1
        for left < right {            total := nums[i] + nums[left] + nums[right]
            if total < 0 {                left++            } else if total > 0 {                right--            } else {                // 找到符合条件的三元组                result = append(result, []int{nums[i], nums[left], nums[right]})
                // 去重:跳过相同的第二个数                for left < right && nums[left] == nums[left+1] {                    left++                }                // 去重:跳过相同的第三个数                for left < right && nums[right] == nums[right-1] {                    right--                }
                // 移动双指针                left++                right--            }        }    }
    return result}

Rust实现

use std::collections::HashSet;
impl Solution {    pub fn three_sum(nums: Vec<i32>) -> Vec<Vec<i32>> {        let mut nums = nums;        // 对数组进行排序        nums.sort();        let mut result = Vec::new();        let n = nums.len();
        // 遍历第一个数        for i in 0..n {            // 确保至少有三个元素            if i + 2 >= n {                break;            }
            // 如果第一个数大于0,后面不可能有三数之和为0            if nums[i] > 0 {                break;            }
            // 去重:跳过相同的第一个数            if i > 0 && nums[i] == nums[i-1] {                continue;            }
            // 双指针初始化            let mut left = i + 1;            let mut right = n - 1;
            while left < right {                let total = nums[i] + nums[left] + nums[right];
                if total < 0 {                    left += 1;                } else if total > 0 {                    right -= 1;                } else {                    // 找到符合条件的三元组                    result.push(vec![nums[i], nums[left], nums[right]]);
                    // 去重:跳过相同的第二个数                    while left < right && nums[left] == nums[left+1] {                        left += 1;                    }                    // 去重:跳过相同的第三个数                    while left < right && nums[right] == nums[right-1] {                        right -= 1;                    }
                    // 移动双指针                    left += 1;                    right -= 1;                }            }        }
        result    }}

C++实现

#include <vector>#include <algorithm>using namespace std;
class Solution {public:    vector<vector<int>> threeSum(vector<int>& nums) {        // 对数组进行排序        sort(nums.begin(), nums.end());        vector<vector<int>> result;        int n = nums.size();
        // 遍历第一个数        for (int i = 0; i < n - 2; i++) {            // 如果第一个数大于0,后面不可能有三数之和为0            if (nums[i] > 0) {                break;            }
            // 去重:跳过相同的第一个数            if (i > 0 && nums[i] == nums[i-1]) {                continue;            }
            // 双指针初始化            int left = i + 1, right = n - 1;
            while (left < right) {                int total = nums[i] + nums[left] + nums[right];
                if (total < 0) {                    left++;                } else if (total > 0) {                    right--;                } else {                    // 找到符合条件的三元组                    result.push_back({nums[i], nums[left], nums[right]});
                    // 去重:跳过相同的第二个数                    while (left < right && nums[left] == nums[left+1]) {                        left++;                    }                    // 去重:跳过相同的第三个数                    while (left < right && nums[right] == nums[right-1]) {                        right--;                    }
                    // 移动双指针                    left++;                    right--;                }            }        }
        return result;    }};

复杂度分析

时间复杂度

  • 排序的时间复杂度:O(n log n)

  • 双指针遍历的时间复杂度:O(n²)

  • 总体时间复杂度:O(n²),其中n是数组的长度

空间复杂度

  • O(log n) 到 O(n),主要取决于排序算法使用的额外空间

  • 如果不算存储结果的空间,算法本身只使用了常数级别的额外空间

拓展思考

相关问题

  1. 三数之和的变体

    • 最接近的三数之和(LeetCode 16)

    • 四数之和(LeetCode 18)

    • 三数之和小于K(类似问题)

  2. 去重技巧

    • 除了排序后跳过相同元素,还可以使用哈希集合来存储结果,自动去重

    • 但哈希集合的方法会使用更多空间,且在某些情况下效率不如排序去重

  3. 优化技巧

    • 可以增加一些剪枝条件,如如果nums[i] + nums[i+1] + nums[i+2] > 0,直接break

    • 如果nums[i] + nums[n-1] + nums[n-2] < 0,直接continue

实际应用场景

三数之和问题虽然是一个算法题,但其中蕴含的双指针技巧和去重思想在实际开发中也有应用:

  1. 数据分析:在数据集中寻找满足特定条件的组合

  2. 金融领域:寻找投资组合使得收益或风险达到特定目标

  3. 搜索优化:在有序数据中高效查找特定和的组合

常见错误与注意事项

  1. 去重时机:必须在找到一个有效三元组后再进行去重操作,否则可能漏掉解

  2. 边界条件:注意数组越界问题,特别是在处理去重时

  3. 指针移动:找到有效三元组后,需要同时移动左右指针

  4. 排序影响:排序会改变原数组顺序,但题目不要求返回原始索引,因此是允许的

总结

LeetCode 15.三数之和是一道经典的数组操作题目,通过排序+双指针的方法可以将时间复杂度优化到O(n²)。解决这道题的关键在于理解双指针的移动逻辑和去重操作。

本文详细解析了题目的解题思路,提供了五种编程语言的实现代码,并对时间和空间复杂度进行了分析。同时,还探讨了相关的拓展问题和实际应用场景。

掌握这道题目的解题技巧,不仅能帮助我们更好地理解双指针算法,还能为解决更复杂的n数之和问题打下基础。希望本文能帮助不同层次的读者深入理解三数之和问题的解法。

【声明】内容源于网络
0
0
点点技术屋
写自己擅长的Nginx,Linux,Redis,JVM和Golang 写自己学习的算法,运维总结 写一些散文随笔 写我最爱的大熊猫,发大熊猫的图片
内容 120
粉丝 0
点点技术屋 写自己擅长的Nginx,Linux,Redis,JVM和Golang 写自己学习的算法,运维总结 写一些散文随笔 写我最爱的大熊猫,发大熊猫的图片
总阅读10
粉丝0
内容120