面试官:三数之和写的不错,来个四数之和吧。我:。。。🤪🤪🤪

96 阅读4分钟

引言

大家好啊,我是前端拿破轮😁。

跟着卡哥学算法有一段时间了,通过代码随想录的学习,受益匪浅,首先向卡哥致敬🫡。

但是在学习过程中我也发现了一些问题,很多当时理解了并且AC的题目过一段时间就又忘记了,或者不能完美的写出来。根据费曼学习法,光有输入的知识掌握的是不够牢靠的,所以我决定按照代码随想录的顺序,输出自己的刷题总结和思考。同时,由于以前学习过程使用的是JavaScript,而在2025年的今天,TypeScript几乎成了必备项,所以本专题内容也将使用TypeScript,来巩固自己的TypeScript语言能力。

题目信息

四数之和

leetcode题目链接

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

题目分析

本题和三数之和非常类似,就是在其外层又多加了一层循环。所以逻辑上和三数之和一致,可以参看笔者前面写的文章😏😏😏不会吧,不会吧,你不会以为三数之和只是比两数之和多了一个数吧?。那么掌握了三数之和,四数之和就肯定没有问题吗?实际上并不是,四数之和除了要掌握三数之和的逻辑外,还要掌握合理的剪枝方式,从而避免无效循环。具体请看代码。

题解

function fourSum(nums: number[], target: number): number[][] {
    // 结果数组
    const result: number[][] = [];

    // 保存数组长度
    const len = nums.length;

    // 剪枝,当数组元素不足四个时直接返回
    if (len < 4) {
        return result;
    }

    // 排序
    nums.sort((a, b) => a - b);

    // 开始遍历第一个数的位置,从0到len-4
    for (let i = 0; i < len - 3; i++) {
        // 剪枝:如果当前循环中最小的四数组合大于target则跳出循环
        if (nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {
            break;
        }
        // 剪枝:如果当前循环中最大的四数组合小于target则跳过本次循环
        if (nums[i] + nums[len - 3] + nums[len - 2] + nums[len - 1] < target) {
            continue;
        }
        // 去重
        if (i > 0 && nums[i] === nums[i - 1]) {
            continue;
        }

        // 开始遍历第二个数的位置,从i+1到len-3
        for (let j = i + 1; j < len - 2; j++) {
            // 剪枝:如果当前循环中最小的四数组合大于target则跳出循环
            if (nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) {
                break;
            }
            // 剪枝:如果当前循环中最大的四数组合小于target则跳过本次循环
            if (nums[i] + nums[j] + nums[len - 2] + nums[len - 1] < target) {
                continue;
            }
            // 去重
            if (j > i + 1 && nums[j] === nums[j - 1]) {
                continue;
            }

            // 定义左右指针
            let left = j + 1;
            let right = len - 1;

            // 开始遍历第三个数和第四个数的位置
            while (left < right) {
                // 计算四数之和
                const sum = nums[i] + nums[j] + nums[left] + nums[right];

                // 判断四数之和与target的大小情况
                if (sum === target) {
                    // 将四数加入结果数组
                    result.push([nums[i], nums[j], nums[left], nums[right]]);
                    
                    // 移动指针
                    left++;
                    right--;

                    // 去重
                    while (left < right && nums[left] === nums[left - 1]) {
                        left++;
                    }
                    while (left < right && nums[right] === nums[right + 1]) {
                        right--;
                    }
                } else if (sum < target) {
                    left++;
                } else {
                    right--;
                }
            }
        }
    }
    return result;
}

时间复杂度:O(n3)O(n^3)

空间复杂度:O(1)O(1),返回结果所占用的空间不统计,只占用了常数级别的指针。

总结

本题在思路上与三数之和一致,关键在于剪枝和去重的逻辑。什么时候需要剪枝?如何剪枝?什么时候需要去重?如何去重?如果无法处理好这些问题,要么造成算法性能下降,要么可能造成漏解错误。

好了,这篇文章就到这里啦,如果对您有所帮助,欢迎点赞,收藏,分享👍👍👍。您的认可是我更新的最大动力。由于笔者水平有限,难免有疏漏不足之处,欢迎各位大佬评论区指正。

往期推荐✨✨✨

我是前端拿破轮,关注我,一起学习前端知识,我们下期见!