React Router

014-四数之和 (4Sum)

LeetCode 第18题 - 四数之和的详细题解

题目描述

给你一个由 n 个整数组成的数组 nums,和一个目标值 target。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]](若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abcd 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案。

示例

输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]

解题思路

先对数组进行排序,然后使用四重循环的优化版本。固定前两个数,使用双指针寻找剩余两个数,同时通过跳过重复元素来避免重复结果。

JavaScript 解决方案

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[][]}
 */
const fourSum = (nums, target) => {
    const result = [];
    const n = nums.length;
    
    // 如果数组长度小于4,返回空数组
    if (n < 4) return result;
    
    // 对数组进行排序
    nums.sort((a, b) => a - b);
    
    for (let i = 0; i < n - 3; i++) {
        // 跳过重复的第一个数
        if (i > 0 && nums[i] === nums[i - 1]) continue;
        
        for (let j = i + 1; j < n - 2; j++) {
            // 跳过重复的第二个数
            if (j > i + 1 && nums[j] === nums[j - 1]) continue;
            
            let left = j + 1;
            let right = n - 1;
            
            while (left < right) {
                const sum = nums[i] + nums[j] + nums[left] + nums[right];
                
                if (sum === target) {
                    result.push([nums[i], nums[j], nums[left], nums[right]]);
                    
                    // 跳过重复的第三个数
                    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 result;
};

复杂度分析

  • 时间复杂度:O(n³),其中 n 是数组长度
  • 空间复杂度:O(1),不考虑输出数组的空间

测试用例

// 测试函数
const testFourSum = () => {
    const testCases = [
        {
            nums: [1, 0, -1, 0, -2, 2],
            target: 0,
            expected: [[-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1]]
        },
        {
            nums: [2, 2, 2, 2, 2],
            target: 8,
            expected: [[2, 2, 2, 2]]
        },
        {
            nums: [1, 0, -1, 0, -2, 2],
            target: 1,
            expected: [[-2, 0, 1, 2], [-1, 0, 0, 2]]
        },
        {
            nums: [],
            target: 0,
            expected: []
        },
        {
            nums: [1, 2, 3, 4],
            target: 10,
            expected: [[1, 2, 3, 4]]
        },
        {
            nums: [0, 0, 0, 0],
            target: 0,
            expected: [[0, 0, 0, 0]]
        }
    ];

    testCases.forEach((testCase, index) => {
        const result = fourSum(testCase.nums, testCase.target);
        // 对结果进行排序以便比较
        const sortedResult = result.map(quad => quad.sort((a, b) => a - b)).sort();
        const sortedExpected = testCase.expected.map(quad => quad.sort((a, b) => a - b)).sort();
        const passed = JSON.stringify(sortedResult) === JSON.stringify(sortedExpected);
        
        console.log(`测试用例 ${index + 1}: ${passed ? '通过' : '失败'}`);
        console.log(`输入: nums = [${testCase.nums}], target = ${testCase.target}`);
        console.log(`期望: [${testCase.expected.map(q => `[${q.join(',')}]`).join(', ')}]`);
        console.log(`实际: [${result.map(q => `[${q.join(',')}]`).join(', ')}]\n`);
    });
};

// 运行测试
testFourSum();

关键点总结

  1. 排序预处理:先对数组排序,便于去重和优化
  2. 四重循环优化:固定前两个数,使用双指针寻找剩余两个数
  3. 去重处理:跳过重复的元素避免重复结果
  4. 双指针技巧:根据和的大小移动左右指针
  5. 边界处理:对数组长度不足的情况的处理

总结

这道题考察了以下重点:

  • 双指针技巧:在排序数组中使用双指针寻找目标值
  • 去重处理:如何避免重复的四元组
  • 算法优化:通过排序和剪枝提高效率
  • 边界处理:对空数组和长度不足的数组的处理

排序+双指针法是最优解,时间复杂度为 O(n³),空间复杂度为 O(1)。在实际面试中,建议先提出暴力解法,然后优化到双指针解法。