# 四数之和 (opens new window)

  • 难度:Medium
  • 标签:排序 双指针

# 刷题思路

  • [x] 排序+双指针夹逼(复用twoSum版本)
  • [x] 排序+双指针夹逼(复用twoSum版本 & 加入剪枝)

# 方法 1 排序+双指针夹逼(复用twoSum版本)

  • 思路:
    • 复用:套用 threeSum 的解题方式,即复用其 twoSum 解题模板
  • 复杂度:
    • 时间 O(n^3). 排序 O(nlogn) + 遍历*左右指针的夹逼 O(n^3).
    • 空间 O(n) 计算 twoSum 时借助了与结果数量相同的额外空间,最好情况即无结果为, 最坏情况结果亦小于N, 故 O(n).
  • 结果:
    • 执行用时:132 ms, 在所有 JavaScript 提交中击败了33.14%的用户
    • 内存消耗:39.9 MB, 在所有 JavaScript 提交中击败了25.47%的用户
var fourSum = function(nums, target) {
    if (!nums || nums.length<4) return []
    nums.sort((a, b) => a-b)
    const res = []
    for (let i=0, len=nums.length; i<len-3; i++) { // target 无限制故无法使用 threeSum 的限制实现剪枝
        if (i>0 && nums[i]===nums[i-1]) continue // 传统去重剪枝
        for (let j=i+1; j<len-2; j++) {
            if (j>i+1 && nums[j]===nums[j-1]) continue
            const arr = twoSum(nums, j+1, target-nums[i]-nums[j])
            arr.forEach(item => res.push([nums[i], nums[j], ...item]))
        }
    }
    return res
};

function twoSum (nums, start, target) {
    let [left, right] = [start, nums.length-1]
    const res = []
    while (left < right) {
        const tmp = nums[left] + nums[right]
        if (tmp === target) {
            res.push([nums[left], nums[right]])
            while (left<right && nums[left]===nums[left+1]) left++
            while (left<right && nums[right]===nums[right-1]) right--
            ;[left, right] = [left+1, right-1]
        } else if (tmp < target) {
            left++
        } else {
            right--
        }
    }
    return res
}

# 方法 2 排序+双指针夹逼(复用twoSum版本 & 加入剪枝)

  • 思路:
    • 复用:复用 threeSum 的解题方式,即套用其 twoSum 解题模板
    • 剪枝:由于 target 无限制故无法使用 threeSum 的限制实现剪枝,使用新的 min 和 max 剪枝有效减少无效计算.
  • 复杂度:
    • 时间 O(n^3). 排序 O(nlogn) + 遍历*左右指针的夹逼 O(n^3).
    • 空间 O(n) 计算 twoSum 时借助了与结果数量相同的额外空间,最好情况即无结果为, 最坏情况结果亦小于N, 故 O(n).
  • 结果:
    • 执行用时:124 ms, 在所有 JavaScript 提交中击败了43.12%的用户
    • 内存消耗:43 MB, 在所有 JavaScript 提交中击败了9.27%的用户
var fourSum = function(nums, target) {
    if (!nums || nums.length<4) return []
    nums.sort((a, b) => a-b)
    const res = []
    for (let i=0, len=nums.length; i<len-3; i++) { // target 无限制故无法使用 threeSum 的限制实现剪枝
        if (i>0 && nums[i]===nums[i-1]) continue // 传统去重剪枝
        if ((nums[i]+nums[i+1]+nums[i+2]+nums[i+3])>target) break // min 剪枝
        if ((nums[i]+nums[len-3]+nums[len-2]+nums[len-1])<target) continue // max 剪枝,退出前会重复N次
        for (let j=i+1; j<len-2; j++) {
            if (j>i+1 && nums[j]===nums[j-1]) continue
            if ((nums[i]+nums[j]+nums[j+1]+nums[j+2])>target) break
            if ((nums[i]+nums[j]+nums[len-2]+nums[len-1])<target) continue
            const arr = twoSum(nums, j+1, target-nums[i]-nums[j])
            arr.forEach(item => res.push([nums[i], nums[j], ...item]))
        }
    }
    return res
};

function twoSum (nums, start, target) {
    let [left, right] = [start, nums.length-1]
    const res = []
    while (left < right) {
        const tmp = nums[left] + nums[right]
        if (tmp === target) {
            res.push([nums[left], nums[right]])
            while (left<right && nums[left]===nums[left+1]) left++
            while (left<right && nums[right]===nums[right-1]) right--
            ;[left, right] = [left+1, right-1]
        } else if (tmp < target) {
            left++
        } else {
            right--
        }
    }
    return res
}

JS刷题记录 Leetcode-js (opens new window) 每周都会更新刷题心得或者题解, 你的点赞或 star 都将助力我产出更好内容~