【力扣Leetcode题解系列之0018—4Sum 四数之和】

18. 4Sum 四数之和:多语言实现、分析与拓展

一、题目分析

给定一个包含 n 个整数的数组 nums 和一个目标整数 target,需要找出数组中所有满足 a + b + c + d = target 的唯一四元组 (a, b, c, d),且结果集中不能包含重复的四元组。

二、常用解法

排序 + 多指针法

  1. 思路
    • 排序:首先对数组 nums 进行排序,这是后续操作的基础。排序后数组元素按升序排列,方便通过指针移动来调整四元组的和。
    • 多层遍历与双指针:使用两个指针进行外层遍历,分别设为 ij,它们的移动范围决定了四元组中前两个数的取值。对于每一对 (i, j),初始化另外两个指针 klk 指向 j + 1l 指向数组末尾。通过移动 kl 来找到满足和为 target 的四元组。
    • 和的比较与指针移动:计算当前四元组的和 sum = nums[i] + nums[j] + nums[k] + nums[l],将其与 target 比较。若 sum > target,则将 l 左移以减小和;若 sum < target,则将 k 右移以增大和;若 sum == target,则找到一个满足条件的四元组,将其加入结果集,并对 kl 进行去重移动,避免重复结果。
    • 去重操作:在移动 ijkl 指针时,都要进行去重操作。例如,当 sum == target 后,通过循环跳过 kl 指向的重复元素;当外层指针 ij 移动时,同样要跳过与前一个位置相同的元素,以确保结果集中不会出现重复的四元组。
  2. 优点:这种方法通过排序和多指针的结合,有效地减少了需要遍历的组合数量,相较于暴力解法的 (O(n^4)) 时间复杂度,此方法将时间复杂度降低到 (O(n^3)),提高了算法效率。

三、多语言实现

Python实现

class Solution:
    def fourSum(self, nums, target):
        N = len(nums)
        nums.sort()
        res = []
        i = 0
        while i < N - 3:
            j = i + 1
            while j < N - 2:
                k = j + 1
                l = N - 1
                remain = target - nums[i] - nums[j]
                while k < l:
                    if nums[k] + nums[l] > remain:
                        l -= 1
                    elif nums[k] + nums[l] < remain:
                        k += 1
                    else:
                        res.append([nums[i], nums[j], nums[k], nums[l]])
                        while k < l and nums[k] == nums[k + 1]:
                            k += 1
                        while k < l and nums[l] == nums[l - 1]:
                            l -= 1
                        k += 1
                        l -= 1
                while j < N - 2 and nums[j] == nums[j + 1]:
                    j += 1
                j += 1
            while i < N - 3 and nums[i] == nums[i + 1]:
                i += 1
            i += 1
        return res

Java实现

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class FourSum {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> res = new ArrayList<>();
        if (nums == null || nums.length < 4) {
            return res;
        }
        Arrays.sort(nums);
        int n = nums.length;
        for (int i = 0; i < n - 3; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            for (int j = i + 1; j < n - 2; j++) {
                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }
                int left = j + 1;
                int right = n - 1;
                while (left < right) {
                    int sum = nums[i] + nums[j] + nums[left] + nums[right];
                    if (sum > target) {
                        right--;
                    } else if (sum < target) {
                        left++;
                    } else {
                        List<Integer> quadruplet = new ArrayList<>();
                        quadruplet.add(nums[i]);
                        quadruplet.add(nums[j]);
                        quadruplet.add(nums[left]);
                        quadruplet.add(nums[right]);
                        res.add(quadruplet);
                        while (left < right && nums[left] == nums[left + 1]) {
                            left++;
                        }
                        while (left < right && nums[right] == nums[right - 1]) {
                            right--;
                        }
                        left++;
                        right--;
                    }
                }
            }
        }
        return res;
    }
}

C实现

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

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

// 辅助函数,用于判断四元组是否已存在
int exists(int **result, int size, int *quadruplet) {
    for (int i = 0; i < size; i++) {
        if (result[i][0] == quadruplet[0] && result[i][1] == quadruplet[1] && result[i][2] == quadruplet[2] && result[i][3] == quadruplet[3]) {
            return 1;
        }
    }
    return 0;
}

int** fourSum(int *nums, int numsSize, int target, int *returnSize, int **returnColumnSizes) {
    if (numsSize < 4) {
        *returnSize = 0;
        return NULL;
    }
    qsort(nums, numsSize, sizeof(int), compare);
    int **result = (int **)malloc(numsSize * numsSize * sizeof(int *));
    *returnColumnSizes = (int *)malloc(numsSize * numsSize * sizeof(int));
    *returnSize = 0;

    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;
            int right = numsSize - 1;
            while (left < right) {
                int sum = nums[i] + nums[j] + nums[left] + nums[right];
                if (sum > target) {
                    right--;
                } else if (sum < target) {
                    left++;
                } else {
                    int *quadruplet = (int *)malloc(4 * sizeof(int));
                    quadruplet[0] = nums[i];
                    quadruplet[1] = nums[j];
                    quadruplet[2] = nums[left];
                    quadruplet[3] = nums[right];
                    if (!exists(result, *returnSize, quadruplet)) {
                        result[*returnSize] = quadruplet;
                        (*returnColumnSizes)[*returnSize] = 4;
                        (*returnSize)++;
                    } else {
                        free(quadruplet);
                    }
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    left++;
                    right--;
                }
            }
        }
    }
    return result;
}

C++实现

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        if (nums.size() < 4) {
            return res;
        }
        sort(nums.begin(), nums.end());
        int n = nums.size();
        for (int i = 0; i < n - 3; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            for (int j = i + 1; j < n - 2; j++) {
                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }
                int left = j + 1;
                int right = n - 1;
                while (left < right) {
                    long long sum = (long long)nums[i] + nums[j] + nums[left] + nums[right];
                    if (sum > target) {
                        right--;
                    } else if (sum < target) {
                        left++;
                    } else {
                        res.push_back({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--;
                    }
                }
            }
        }
        return res;
    }
};

Go实现

package main

import (
    "fmt"
    "sort"
)

func fourSum(nums []int, target int) [][]int {
    var res [][]int
    if len(nums) < 4 {
        return res
    }
    sort.Ints(nums)
    for i := 0; i < len(nums)-3; i++ {
        if i > 0 && nums[i] == nums[i - 1] {
            continue
        }
        for j := i + 1; j < len(nums)-2; j++ {
            if j > i + 1 && nums[j] == nums[j - 1] {
                continue
            }
            left, right := j + 1, len(nums) - 1
            for left < right {
                sum := nums[i] + nums[j] + nums[left] + nums[right]
                if sum > target {
                    right--
                } else if sum < target {
                    left++
                } else {
                    res = append(res, []int{nums[i], nums[j], nums[left], nums[right]})
                    for left < right && nums[left] == nums[left + 1] {
                        left++
                    }
                    for left < right && nums[right] == nums[right - 1] {
                        right--
                    }
                    left++
                    right--
                }
            }
        }
    }
    return res
}

四、算法复杂性分析

时间复杂度

  1. 排序部分:对长度为 n 的数组进行排序,常见排序算法如快速排序、归并排序平均时间复杂度为 (O(n \log n))。
  2. 多层遍历与双指针部分:外层有两层循环,时间复杂度为 (O(n^2)),对于每一对外层循环的指针,内层双指针移动次数最多为 (n) 次,所以这部分时间复杂度为 (O(n))。总体时间复杂度为 (O(n^2 \times n) = O(n^3)),因为 (O(n^3)) 增长速度快于 (O(n \log n)),所以整个算法时间复杂度为 (O(n^3))。

空间复杂度

  1. 除输入数组外:使用的额外空间主要是存储结果集的空间,在最坏情况下,结果集可能包含 (O(n^3)) 个四元组(尽管实际中通常远小于这个数量)。此外,还使用了一些指针变量等常数级空间。如果不考虑输出空间,算法使用的额外空间为常数级,即 (O(1))。所以空间复杂度主要由结果集决定,在最坏情况下为 (O(n^3))。

五、实现的关键点和难度

关键点

  1. 排序:排序是整个算法的基础步骤,它使得数组有序,为后续通过指针移动来调整四元组的和提供了可能。通过排序,我们可以利用数组元素的顺序特性,快速判断和调整四元组的和与目标值的关系。
  2. 多层指针遍历与移动逻辑:合理设置多层指针的初始位置和移动条件是核心。外层两层指针遍历确定四元组中的前两个数,内层双指针遍历寻找满足和为 target 的后两个数。指针根据和与目标值的比较结果正确移动,保证能够遍历到所有可能的四元组组合。
  3. 去重操作:在指针移动过程中,对重复元素进行跳过操作,确保结果集中不会出现重复的四元组。这需要在每一层指针移动时都仔细处理,避免遗漏或错误地保留重复结果。

难度

  1. 多层循环嵌套与指针管理:多层循环嵌套增加了代码的复杂度,需要清晰地理解每一层循环的作用和边界条件。同时,多个指针的移动和管理需要谨慎处理,确保它们在正确的范围内移动,并且相互之间的配合能够正确遍历所有可能的组合。
  2. 去重逻辑的实现:去重逻辑在多层循环和指针移动的过程中实现,需要在不同的指针移动场景下都正确处理重复元素。例如,在找到一个满足条件的四元组后,不仅要对双指针指向的元素去重,还要对外层指针移动时的重复元素进行处理,这要求对算法的整体流程有深入的理解,以确保去重的完整性和正确性。

六、扩展及难度加深题目

扩展题目1:K数之和

给定一个数组 nums 和一个目标值 target,找出数组中所有满足 (a_1 + a_2 + \cdots + a_k = target的唯一k元组。可以将四数之和的方法扩展到一般的k数之和问题,时间复杂度为 \(O(n^{k - 1})\),空间复杂度在最坏情况下为 \(O(n^{k - 1})\)。实现过程中需要处理多层循环和指针的移动以及去重逻辑,随着k` 的增大,复杂度会显著增加。

扩展题目2:带权重的四数之和

给数组中的每个元素赋予一个权重,要求找出所有满足四数之和为 target 的四元组,并且使得四元组的权重之和最大(或最小)。这需要在寻找四元组的过程中,同时考虑权重因素,增加了算法的复杂性。不仅要关注数字之和,还要计算权重之和,并根据权重之和进行比较和筛选。

难度加深题目1:动态更新数组的四数之和

假设数组会动态更新(添加或删除元素),要求在每次更新后能够高效地更新满足四数之和为 target 的四元组集合。这可能需要使用更复杂的数据结构(如平衡二叉搜索树)来维护数组元素,以便在更新后快速重新计算结果。例如,使用红黑树来存储数组元素,在添加或删除元素后,通过调整树的结构来快速定位可能影响四数之和结果的元素范围,并重新计算。

难度加深题目2:多维数组的四数之和

给定一个多维数组(如二维数组,每个元素又是一个数组),要求在这些数组中找出所有满足四数之和为 target 的组合。这需要考虑如何遍历多维数组,并在不同维度上应用类似的算法思想。例如,可能需要先确定在多维数组中如何选择四个元素构成组合,然后通过排序和指针移动的方式来找到满足和为 target 的组合,实现起来较为复杂,需要处理好不同维度之间的关系和组合的唯一性。

七、应用场合

  1. 数据分析与挖掘:在数据分析中,可能需要从大量数据中找出满足特定数值关系的组合。例如,在金融数据分析中,寻找满足特定资金流动总和的四笔交易记录,通过四数之和问题的解法可以帮助发现潜在的交易模式或异常情况。
  2. 组合优化问题:在资源分配、任务调度等场景中,可能需要从多个资源或任务的参数中找出特定组合,使得它们的和满足某个条件。例如,在项目管理中,从多个任务的成本、时间等参数中找出四个任务,使得它们的总成本或总时间最接近目标值,四数之和问题的解法可以为这类组合优化问题提供思路和方法。
  3. 算法竞赛与面试:此类问题作为经典的算法问题,常用于算法竞赛和面试中,考察应聘者对数组操作、排序算法、多指针法以及去重等技术的掌握和应用能力。通过解决这类问题,可以检验应聘者的逻辑思维、代码实现能力以及对复杂问题的分析和解决能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值