题目来源

15. 三数之和 - 力扣(LeetCode)

题目描述

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

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

示例 1


示例 2


示例 3

提示

  • 3 <= nums.length <= 3000
  • -10^5 <= nums[i] <= 10^5

题目解析

本题我们可以将 nums 进行升序

选取三元组时,我们可以固定 i 索引位置,然后初始 j = i + 1,k = nums.length - 1,如下图所示:

LeetCode - 15 三数之和-LMLPHP

然后计算此时三元组之和:sum = nums[i] + nums[j] + nums[k]

  • 若 sum > 0,则说明三元组之和偏大了,此时我们应该 k--,这样的话,三元组之和就会减小
  • 若 sum < 0,则说明三元组之和偏小了,此时我们应该 j++,这样的话,三元组之和就会增大
  • 若 sum == 0,则说明此时三元组是符合要求的,我们记录此时的三元组,然后 k-- ,j++

对于 nums = [-4, -1, -1, 0, 1, 2],按照上面逻辑演示,过程如下:

LeetCode - 15 三数之和-LMLPHP

本题需要我们求解不重复的三元组,但是我们通过上图发现,存在重复的三元组 [-1, 0, 1],而造成重复的原因是 nums[1] == nums[2]。

如果 i = 1,那么可以产生的三元组(索引)有:

  • [i=1, j=2, k=5]
  • [i=1, j=2, k=4]
  • [i=1, j=2, k=3]
  • [i=1, j=3, k=5]
  • [i=1, j=3, k=4]
  • [i=1, j=4, k=5]

如果 i = 2,那么可以产生的三元组(索引)有:

  • [i=2, j=3, k=5]
  • [i=2, j=3, k=4]
  • [i=2, j=4, k=5]

那么,nums[1] == nums[2] 的话,那么 i = 1可以产生的三元组是可以完全覆盖 i = 2 产生的三元组的(三元组各元素值相同)。

因此当 nums[i] == nums[i-1] 时,我们就可以跳过 nums[i] 产生三元组的过程,因为必然和 nums[i-1] 产生的三元组发生重复。

除了上面情况会产生重复三元组外,还有一种情况,比如 nums = [-2, 0, 0, 2, 2],如下图所示,会产生重复三元组 [-2, 0, 2]

LeetCode - 15 三数之和-LMLPHP

造成重复的原因是:

  • 如果 j++ 后,nums[j] == nums[j - 1],则会产生重复三元组
  • 如果 k-- 后,nums[k] == nums[k+1],则会产生重复三元组

为了避免产生重复三元组,我们可以在 j++ 或 k-- 过程中,跳过相同元素。

C源码实现

C语言这题比较难以理解是函数参数 int* returnSize 和 int** returnColumnSizes。

我们返回的结果是一个 int** 类型的,主要记录不重复的三元组,比如 [[-1,-1,2], [-1,0,1]],可以发现返回值本质是一个二维数组。

而 returnSize 记录的其实就是返回值二维数组的行数,returnColumnSizes记录的是返回值二维数组每行的列数。

比如对于返回值 [[-1,-1,2], [-1,0,1]] 来说,returnSize = 2,returnColumnSizes = [3, 3]

而本题 returnSize 和 returnColumnSizes 参数类型都是指针,即调用 threeSum 函数时,传递的时 returnSize 和 returnColumnSizes 的指针(地址)。大家可以对照下面代码注释中 main 函数调用 threeSum函数 时的传参来理解。

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

/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume
 * caller calls free().
 */
int cmp(const void *a, const void *b) { return *(int *) a - *(int *) b; }

int **threeSum(int *nums, int numsSize, int *returnSize, int **returnColumnSizes) {
    // 记录结果
    int **res = (int **) malloc(sizeof(int *) * 20000);
    int res_size = 0;

    // nums升序
    qsort(nums, numsSize, sizeof(nums[0]), cmp);

    for (int i = 0; i < numsSize - 2; i++) { // i作为三元组中最小元素的索引位置
        if (nums[i] > 0) {
            // 由于nums已经升序,因此nums[i]>0时, 而 nums[r] >= nums[l] >= nums[i], 因此之后三数之和必然大于0
            break;
        }

        if (i > 0 && nums[i] == nums[i - 1]) {
            // 去重
            continue;
        }

        // 双指针
        int l = i + 1;
        int r = numsSize - 1;

        while (l < r) {
            // 三数之和
            int sum = nums[i] + nums[l] + nums[r];

            if (sum > 0) {
                // r左移, 三数之和减少
                r--;
            } else if (sum < 0) {
                // l右移, 三数之和增加
                l++;
            } else {
                // 三数之和为0时, 此时三元组是符合题目要求的
                res[res_size++] = (int *) malloc(sizeof(int) * 3);
                res[res_size - 1][0] = nums[i];
                res[res_size - 1][1] = nums[l];
                res[res_size - 1][2] = nums[r];

                // 去重
                do {
                    l++;
                } while (l < r && nums[l] == nums[l - 1]);

                // 去重
                do {
                    r--;
                } while (r > l && nums[r] == nums[r + 1]);
            }
        }
    }

    *returnSize = res_size; // returnSize 记录 res 行数, 本质是一个int值, 只是参数传递时, 取地址变为了 int* 类型

    *returnColumnSizes = (int *) malloc(sizeof(int) * res_size); // returnColumnSizes 记录 res 每一行的列数, 本质是一个int一维数组, 只是参数传递时, 取地址变为了 int** 类型
    for (int i = 0; i < res_size; i++) {
        (*returnColumnSizes)[i] = 3;
    }

    return res;
}

//int main() {
//    int nums[] = {-1, 0, 1, 2, -1, -4};
//    int numSize = 6;
//
//    int returnSize; // 记录结果行数
//    int *returnColumnSizes; // 记录结果的每一行列数
//
//    // int returnSize 取地址后变为 int* 类型
//    // int* returnColumnSizes 取地址后变为 int** 类型
//    int **res = threeSum(nums, numSize, &returnSize, &returnColumnSizes);
//
//    for (int i = 0; i < returnSize; i++) {
//        printf("[");
//        for (int j = 0; j < returnColumnSizes[i]; j++) {
//            printf("%d", res[i][j]);
//
//            if (j < returnColumnSizes[i] - 1) {
//                printf(",");
//            }
//        }
//        printf("]\n");
//    }
//
//
//    return 0;
//}

C++源码实现

class Solution {
public:
    vector<vector<int>> threeSum(vector<int> &nums) {
        vector<vector<int>> res;

        // nums升序
        sort(nums.begin(), nums.end());

        for (int i = 0; i < nums.size() - 2; i++) { // i作为三元组中最小元素的索引位置
            if (nums[i] > 0) {
                // 由于nums已经升序,因此nums[i]>0时, 而 nums[r] >= nums[l] >= nums[i], 因此之后三数之和必然大于0
                break;
            }

            if (i > 0 && nums[i] == nums[i - 1]) {
                // 去重
                continue;
            }

            // 双指针
            int l = i + 1;
            int r = nums.size() - 1;

            while (l < r) {
                // 三数之和
                int sum = nums[i] + nums[l] + nums[r];

                if (sum > 0) {
                    // r左移, 三数之和减少
                    r--;
                } else if (sum < 0) {
                    // l右移, 三数之和增加
                    l++;
                } else {
                    // 三数之和为0时, 此时三元组是符合题目要求的
                    res.emplace_back(vector<int>{nums[i], nums[l], nums[r]});

                    // 去重
                    do {
                        l++;
                    } while (l < r && nums[l] == nums[l - 1]);

                    // 去重
                    do {
                        r--;
                    } while (r > l && nums[r] == nums[r + 1]);
                }
            }
        }

        return res;
    }
};

Java源码实现

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> lists = new ArrayList<>();

        // nums升序
        Arrays.sort(nums);

        for (int i = 0; i < nums.length - 2; i++) {
            if (nums[i] > 0) {
                // 由于nums已经升序,因此nums[i]>0时, 而 nums[r] >= nums[l] >= nums[i], 因此之后三数之和必然大于0
                break;
            }

            if (i > 0 && nums[i] == nums[i - 1]) {
                // 去重
                continue;
            }

            // 双指针
            int l = i + 1;
            int r = nums.length - 1;

            while (l < r) {
                // 三数之和
                int sum = nums[i] + nums[l] + nums[r];

                if (sum > 0) {
                    // r左移, 三数之和减少
                    r--;
                } else if (sum < 0) {
                    // l右移, 三数之和增加
                    l++;
                } else {
                    // 三数之和为0时, 此时三元组是符合题目要求的
                    ArrayList<Integer> list = new ArrayList<>();
                    Collections.addAll(list, nums[i], nums[l], nums[r]);
                    lists.add(list);

                    // 去重
                    do {
                        r--;
                    } while (r > l && nums[r] == nums[r + 1]);

                    // 去重
                    do {
                        l++;
                    } while (l < r && nums[l] == nums[l - 1]);
                }
            }
        }

        return lists;
    }
}

Python源码实现

class Solution(object):
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        # nums升序
        nums.sort()

        res = []

        for i in range(len(nums) - 2):  # i作为三元组中最小元素的索引位置
            if nums[i] > 0:
                # 由于nums已经升序,因此nums[i]>0时, 而 nums[r] >= nums[l] >= nums[i], 因此之后三数之和必然大于0
                break

            if i > 0 and nums[i] == nums[i - 1]:
                # 去重
                continue

            # 双指针
            l = i + 1
            r = len(nums) - 1

            while l < r:
                # 三数之和
                total = nums[i] + nums[l] + nums[r]

                if total > 0:
                    # r左移, 三数之和减少
                    r -= 1
                elif total < 0:
                    # l右移, 三数之和增加
                    l += 1
                else:
                    # 三数之和为0时, 此时三元组是符合题目要求的
                    res.append((nums[i], nums[l], nums[r]))

                    l += 1
                    r -= 1

                    # 去重
                    while l < r and nums[l] == nums[l - 1]:
                        l += 1
                        continue

                    # 去重
                    while r > l and nums[r] == nums[r + 1]:
                        r -= 1
                        continue

        return res

JavaScript源码实现

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function (nums) {
  // nums升序
  nums.sort((a, b) => a - b);

  const res = [];

  for (let i = 0; i < nums.length - 2; i++) {
    // i作为三元组中最小元素的索引位置
    if (nums[i] > 0) {
      // 由于nums已经升序,因此nums[i]>0时, 而 nums[r] >= nums[l] >= nums[i], 因此之后三数之和必然大于0
      break;
    }

    if (i > 0 && nums[i] == nums[i - 1]) {
      // 去重
      continue;
    }

    // 双指针
    let l = i + 1;
    let r = nums.length - 1;

    while (l < r) {
      // 三数之和
      const sum = nums[i] + nums[l] + nums[r];

      if (sum > 0) {
        // r左移, 三数之和减少
        r--;
      } else if (sum < 0) {
        // l右移, 三数之和增加
        l++;
      } else {
        // 三数之和为0时, 此时三元组是符合题目要求的
        res.push([nums[i], nums[l], nums[r]]);

        // 去重
        do {
          l++;
        } while (l < r && nums[l] == nums[l - 1]);

        // 去重
        do {
          r--;
        } while (r > l && nums[r] == nums[r + 1]);
      }
    }
  }

  return res;
};
09-10 14:58