引言

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

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

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

题目信息

四数之和

leetcode题目链接

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

题目分析

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

题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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(n^3)$

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

总结

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

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

往期推荐✨✨✨

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