N-Sum 的算法思想与模板

终结 N-Sum 的算法思想与模板:以 3-Sum 和 4-Sum 为例

在算法面试和 LeetCode 中,N-Sum 问题是一个经典的考察点。无论是 3-Sum,还是 4-Sum,这些问题都是基于同一个核心思想:排序 + 双指针收缩。本文将通过 3-Sum4-Sum 两个经典问题来详细解析 N-Sum 问题的解决思路、技巧与模板。


一、N-Sum 问题的核心思想

1.1 问题的背景

N-Sum 问题的核心是给定一个整数数组,求该数组中 N 个数之和等于目标值 target 的所有唯一组合。以 3-Sum 为例,它的目标是找到数组中三个数的和等于目标值。类似地,4-Sum 就是四个数之和等于目标值。

具体题目描述如下:

  • 3-Sum:给定一个整数数组 nums,找出所有三数之和为零的组合。
  • 4-Sum:给定一个整数数组 nums,找出所有四数之和为目标值 target 的组合。

1.2 N-Sum 的解决核心思想

N-Sum 问题的核心思想是:

  1. 排序数组:首先对数组进行排序。排序可以帮助我们利用双指针来减少不必要的计算,并且在后续步骤中轻松实现去重操作。
  2. 固定一个数:通过固定一个数来将问题降维。例如,在 3-Sum 中固定第一个数,问题变成在剩下的部分找两数之和为目标值。对于 4-Sum,固定前两个数,问题变成了 2-Sum 问题。
  3. 双指针夹逼:固定一个数后,使用双指针从数组两端夹逼,逐步寻找符合条件的组合。根据当前和与目标值的比较,移动左右指针来调整和的大小。
  4. 去重:避免重复的组合。在每一层固定值后,我们需要跳过重复的元素,以免生成重复的结果。

通过这样的思想,我们可以将时间复杂度从暴力枚举的 O(n^N) 降低到 O(n^2)


二、3-Sum 的算法实现

2.1 问题描述

给定一个包含 n 个整数的数组 nums,判断是否存在三个整数使得它们的和为零。如果存在,返回所有不重复的三元组。

2.2 3-Sum 的解法

  1. 排序数组:首先对 nums 数组进行排序,使得数组从小到大排列。
  2. 固定一个数:我们用一个指针固定第一个数,然后用另外两个指针来查找剩余部分。
  3. 双指针查找两数之和:在固定第一个数后,问题就变成了 2-Sum 问题,利用双指针查找剩余两个数的和。
  4. 去重处理:我们在每次固定一个数后,要跳过重复的数。

2.3 代码实现

#include <stdio.h>
#include <stdlib.h>

// 比较函数,用于排序
int cmp(const void* a, const void* b) {
    return (*(int*)a - *(int*)b);
}

int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
    int** ans = (int**)malloc(sizeof(int*) * 20000);
    *returnColumnSizes = (int*)malloc(sizeof(int) * 20000);
    *returnSize = 0;

    if (numsSize < 3) return ans;

    // 排序
    qsort(nums, numsSize, sizeof(int), cmp);

    for (int i = 0; i < numsSize - 2; i++) {
        // 去重
        if (i > 0 && nums[i] == nums[i - 1]) continue;

        int left = i + 1, right = numsSize - 1;
        while (left < right) {
            int sum = nums[i] + nums[left] + nums[right];
            if (sum == 0) {
                ans[*returnSize] = (int*)malloc(sizeof(int) * 3);
                ans[*returnSize][0] = nums[i];
                ans[*returnSize][1] = nums[left];
                ans[*returnSize][2] = nums[right];
                (*returnColumnSizes)[*returnSize] = 3;
                (*returnSize)++;

                // 去重
                while (left < right && nums[left] == nums[left + 1]) left++;
                while (left < right && nums[right] == nums[right - 1]) right--;

                left++;
                right--;
            } else if (sum < 0) {
                left++;
            } else {
                right--;
            }
        }
    }

    return ans;
}

2.4 解释

  1. 排序:通过 qsort 排序数组,方便后续的双指针夹逼。
  2. 固定一个数:每次固定一个数 nums[i],然后使用双指针查找 nums[left]nums[right],使得三数之和为零。
  3. 去重:在每次固定 i 后,跳过重复的元素,以避免重复的三元组。

三、4-Sum 的算法实现

3.1 问题描述

给定一个包含 n 个整数的数组 nums 和目标值 target,判断是否存在四个整数使得它们的和为 target。如果存在,返回所有不重复的四元组。

3.2 4-Sum 的解法

  1. 排序数组:首先对 nums 数组进行排序,使得数组从小到大排列。
  2. 固定两个数:通过两个指针 ij 分别固定前两个数,然后将问题降维为 2-Sum 问题。
  3. 双指针查找两数之和:在固定前两个数后,使用双指针查找剩余两个数的和。
  4. 去重处理:跳过重复的元素,避免重复的四元组。

3.3 代码实现

int** fourSum(int* nums, int numsSize, int target, int* returnSize, int** returnColumnSizes) {
    int** ans = (int**)malloc(sizeof(int*) * 20000);
    *returnColumnSizes = (int*)malloc(sizeof(int) * 20000);
    *returnSize = 0;

    if (numsSize < 4) return ans;

    qsort(nums, numsSize, sizeof(int), cmp);

    for (int i = 0; i < numsSize - 3; i++) {
        if (i > 0 && nums[i] == nums[i - 1]) continue;
        for (int j = i + 1; j < numsSize - 2; j++) {
            if (j > i + 1 && nums[j] == nums[j - 1]) continue;

            int left = j + 1, right = numsSize - 1;
            while (left < right) {
                int sum = nums[i] + nums[j] + nums[left] + nums[right];
                if (sum == target) {
                    ans[*returnSize] = (int*)malloc(sizeof(int) * 4);
                    ans[*returnSize][0] = nums[i];
                    ans[*returnSize][1] = nums[j];
                    ans[*returnSize][2] = nums[left];
                    ans[*returnSize][3] = nums[right];
                    (*returnColumnSizes)[*returnSize] = 4;
                    (*returnSize)++;

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

                    left++;
                    right--;
                } else if (sum < target) {
                    left++;
                } else {
                    right--;
                }
            }
        }
    }

    return ans;
}

3.4 解释

  1. 排序:通过排序,我们可以方便地进行双指针操作,并且去除重复的结果。
  2. 固定两个数:通过两个指针 ij 固定前两个数,将问题简化为 2-Sum 问题。
  3. 双指针查找剩余两个数:剩余的两个数通过双指针 leftright 查找。
  4. 去重:每次固定一个数时,需要跳过重复元素,避免重复结果。

四、总结与模板

4.1 3-Sum 和 4-Sum 模板

  1. 排序:先排序数组。
  2. 固定前几个数:固定第一个数或者前两个数。
  3. 双指针:利用双指针找剩下的两个数。
  4. 去重:跳过重复的元素,避免重复的组合。

4.2 时间复杂度

  • 排序:O(n log n)
  • 外层循环:O(n)
  • 内层双指针:O(n)

总体时间复杂度为:O(n^2)

4.3 使用模板

无论是 3-Sum 还是 4-Sum,基于排序和双指针的思路是最标准和高效的解法。可以通过这种方式解决所有类似的 N-Sum 问题。


posted @ 2026-01-18 16:16  Leon_LL  阅读(0)  评论(0)    收藏  举报