优选算法 力扣 15. 三数之和 双指针降低时间复杂度 C++题解 每日一题

发布于:2025-08-10 ⋅ 阅读:(13) ⋅ 点赞:(0)

一、题目描述

题目链接:三数之和

题目描述:
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i、j、k 互不相同,且 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。注意:答案中不可以包含重复的三元组。

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

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

示例 3:
输入:nums = [0,0,0]
输出:[]

提示:
0 <= nums.length <= 3000
-10^5 <= nums[i] <= 10^5

二、解题思路

三数之和问题可以基于两数之和的双指针思路进行扩展,核心是固定一个元素,将问题转化为两数之和。具体步骤如下:

  1. 排序预处理:对数组进行排序,为后续去重和双指针移动提供基础。
  2. 固定第三个元素:从数组末尾开始,依次固定一个元素 nums[i],则问题转化为在 [0, i-1] 范围内寻找两个元素,使得它们的和为 -nums[i]
  3. 双指针搜索:在 [0, i-1] 范围内使用左右双指针(left 起始于 0,right 起始于 i-1),根据三数之和与 0 的大小关系移动指针:
    • 若和小于 0:左指针右移(增大总和)
    • 若和大于 0:右指针左移(减小总和)
    • 若和等于 0:记录三元组,并移动指针跳过重复元素
  4. 去重优化:在固定元素和移动双指针时,通过跳过重复值避免生成重复三元组。

三、代码实现

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(), nums.end());  // 排序,为去重和双指针做准备
        int size = nums.size();
        vector<vector<int>> ret;  // 存储结果
        int i = size - 1;  // 从末尾开始固定第三个元素
        
        while (i >= 0) {
            int left = 0, right = i - 1;  // 双指针范围:[0, i-1]
            while (left < right) {
                int sum = nums[left] + nums[right] + nums[i];
                if (sum < 0) {
                    left++;  // 总和偏小,左指针右移增大总和
                } else if (sum > 0) {
                    right--;  // 总和偏大,右指针左移减小总和
                } else {
                    // 找到符合条件的三元组
                    ret.push_back({nums[left], nums[right], nums[i]});
                    left++;
                    right--;
                    // 跳过左指针重复元素
                    while (left < right && nums[left] == nums[left - 1]) {
                        left++;
                    }
                    // 跳过右指针重复元素
                    while (left < right && nums[right] == nums[right + 1]) {
                        right--;
                    }
                }
            }
            // 固定元素去重:跳过与当前i相同的元素
            i--;
            while (i >= 0 && nums[i + 1] == nums[i]) {
                i--;
            }
        }
        return ret;
    }
};

四、越界问题深度分析

在三数之和的双指针实现中,越界风险主要集中在指针移动和数组访问的边界处理上,以下是关键分析:

1. 固定元素 i 的越界防护

  • 初始值isize-1 开始(数组最后一个元素),确保 nums[i] 访问有效。
  • 循环条件while (i >= 0) 确保 i 不会小于 0,避免访问 nums[-1] 等无效索引。
  • 去重逻辑while (i >= 0 && nums[i + 1] == nums[i]) 中,i >= 0 是前置条件,防止 i-1i+1 越界(例如 i=0i+1=1 有效,但需确保 i 不小于 0 才能进入循环)。

2. 双指针 leftright 的越界防护

  • 初始范围left 从 0 开始,righti-1 开始,由于 i >= 0right 初始值最大为 size-2(当 i=size-1 时),确保 nums[right] 有效。
  • 循环条件while (left < right) 保证 left 始终在 right 左侧,避免两者交叉后访问无效索引(例如 left = right + 1 时的越界)。
  • 去重逻辑
    • while (left < right && nums[left] == nums[left - 1]) 中,left < right 确保 left 不会超过右边界,left - 1 >= 0(因 left 初始为 0,且只有 left++ 后才可能进入该循环,此时 left >= 1)。
    • while (left < right && nums[right] == nums[right + 1]) 中,right + 1 <= i-1 + 1 = i(因 right 初始为 i-1),而 i <= size-1,故 right + 1 <= size-1,避免访问越界。

3. 总结:为何此代码不会越界?

  • 所有数组访问(nums[left]nums[right]nums[i])的索引均通过循环条件和指针移动逻辑被限制在 [0, size-1] 范围内。
  • 去重逻辑中的指针移动均有前置条件(如 left < righti >= 0),防止指针超出有效范围。

五、去重逻辑解析

三数之和的核心难点之一是避免重复三元组,代码通过三层去重确保结果唯一:

  1. 固定元素去重

    • nums[i]nums[i+1] 相同时,跳过当前 ii--),避免因固定相同元素导致重复三元组。
    • 例如:nums = [-1, -1, 2],若不去重,i=2(值 2)时会重复记录 [-1, -1, 2]
  2. 左指针去重

    • 找到有效三元组后,若 nums[left]nums[left-1] 相同,left++ 跳过,避免重复使用相同元素。
    • 例如:nums = [-2, 0, 0, 2, 2],避免因 left 指向不同位置的 0 或 2 导致重复。
  3. 右指针去重

    • 类似左指针,若 nums[right]nums[right+1] 相同,right-- 跳过。

六、复杂度分析

复杂度类型 具体值 说明
时间复杂度 O(n²) 排序耗时 O(n log n),外层循环 O(n),内层双指针循环 O(n),整体由 O(n²) 主导
空间复杂度 O(log n) 或 O(n) 取决于排序算法的空间复杂度(如快速排序为 O(log n),归并排序为 O(n)),结果存储不计入额外空间

七、举一反三

三数之和的“固定一个元素 + 双指针”思路可推广到:

  • N数之和问题(如明天的四数之和):固定 N-2 个元素,转化为两数之和。
  • 带约束的组合问题:如寻找和为目标值的 k 个元素,且元素不重复。

下一题预告LeetCode 18. 四数之和!这道题在三数之和基础上增加了一个固定元素,需要处理更多层的去重逻辑和边界条件,同时需注意目标值不是固定的 0 而是输入的 target,进一步考验对多指针和去重的掌握。

✨ 如果你觉得这篇解析有帮助,不妨:

🌟 点个赞,让更多人看到这份实用思路
⭐ 收个藏,下次遇到类似问题能快速复习
👀 加个关注,明天的三数之和解析更精彩

这是封面原图嘿嘿
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到