开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第18天,点击查看活动详情。
四数之和
描述
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d 使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意: 答案中不可以包含重复的四元组。
难度
中等
示例
输入: nums = [1, 0, -1, 0, -2, 2],和 target = 0
输出:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]
思路
本题思路和三数之和解题思路一样,但可以使用一种通用的解法,用来解决四数之和、五数之和和六数之和等等。
三数之和的解题思路如下:
为了保证结果不重复。我们先将数组按照从小到大排序,为了不重复,需满足以下条件:
- 第二个元素不小于第一个元素
- 第三个元素不小于第二个元素
先枚举所有的元素,随后使用两个指针,left 表示左指针,right 表示右指针,从大于第一个元素的数组中找出所有两个数之和等于 target - nums[i] 的两个元素,再和第一层循环的元素进行配对,组成三个元素为一组的数组放入结果集中。
第一层循环时需要去掉和上一次循环相同的元素,当从剩余的数组中找到两个元素之后,从 left 指针开始向后寻找一个不等于当前 left 指针指向的元素的下标,从 right 指针开始向前寻找一个不等于当前 right 指针指向的元素的下标,然后继续循环,直到 left 大于 right。
通过三数之和可以发现,第一个数就是枚举数组的所有元素,然后从剩下的数组中寻找两个数之和。我们将三数之和拆分成一层循环 + 两数之和,求出所有不重复的两数之和放入集合中,然后和第一层循环的每个元素做笛卡尔积,组成所有三数之和等于 target 的集合。
如果要计算四数之和,拆分成两层循环 + 两数之和。第一层循环枚举所有元素,然后第二次循环从第一层循环的下一个元素开始枚举所有元素,设第一层循环时下标为 i,第二层循环时下表为 j,第二层循环要求剩下的三数之和为 target - nums[i],最后求出和为 target - nums[i] - nums[j] 的两个数。示例代码如下:
for (i = 0; i < nums.length; i++) {
for (j = i + 1; j < nums.length; j++) {
twoSum(nums, target - nums[i] - nums[j]);
}
}
最后将 num[i], nums[j] 和剩下的两个数做笛卡尔积,得到的集合就是四数之和。
为了达到通用的设计,我们采用递归的方式,递归函数设计如下:
nSum(int[] nums, int start, int end, int target, int n, int[] records, int[] res)
- start:当前起始下标
- end:当前结束下标
- target:目标和
- n:计算第几个数之和
- records:记录第几个数是多少
- res:最终的结果
两数之和的函数设计如下:
int[][] twoSum(int nums, int start, int end, int target)
返回结果为所有两数之和的集合。
递归调用 nSum , 当 n > 2 时,迭代剩余的元素,当 n < 2 时,调用 twoSum 计算两数之和,判断返回结果,如果没有值,表示没有满足目标和的四数,否则处理结果,从 records 中取出前两个数和剩下的所有两个数做笛卡尔积,最后将结果放入 res 中。
首次调用 nSum 时,n 传入 4,以后每调用一次,递减:
nSum(nums, 0, len(nums) - 1, target, 4, records)
nSum 函数伪代码实现如下:
nSum(int[] nums, int start, int end, int target, int n, int[] records, int[] res) {
if (n <= 2) {
// 计算两数之和
twoSum(nums, start, end, target);
// 处理结果
retrun;
}
int i = start
for (;i <= end; i++) {
if (i + 1 <end) {
nSum(nums, i + 1, end, target - nums[i], n - 1, records, res);
}
}
}
代码
Rust
pub struct Solution {}
impl Solution {
pub fn four_sum(nums: Vec<i32>, target: i32) -> Vec<Vec<i32>> {
let mut nums = nums;
nums.sort();
let mut res: Vec<Vec<i32>> = Vec::new();
let mut records = [i32::MIN; 4];
let len = nums.len();
if len == 0 {
return res
}
Solution::n_sum(&mut nums, 0, len - 1, target, 4, &mut records, &mut res);
res
}
/// 计算 n 个数之和
fn n_sum(nums: &Vec<i32>, start: usize, end: usize, target: i32, n: u32, records: &mut [i32], res: &mut Vec<Vec<i32>>) {
let record_len = records.len();
if n <= 2 {
// 计算两数之和
let results: Vec<Vec<i32>> = Solution::two_sum(nums, start, end, target);
// 存在两数之和等于目标值
if results.len() > 0 {
// 循环遍历结果, 将结果添加到最后的结果中
for result in results {
records[record_len - 2] = result[0];
records[record_len - 1] = result[1];
let mut list = Vec::new();
for record in records.iter() {
list.push(*record);
}
res.push(list);
}
}
return;
}
let mut i = start;
while i <= end {
// 如果元素重复, 跳过
if nums[i] == records[record_len - n as usize] {
i += 1;
continue;
}
// 修改当前第 n 个数
records[record_len - n as usize] = nums[i];
if i + 1 < end {
// 继续计算第 n - 1 个数之和
Solution::n_sum(nums, i + 1, end, target - nums[i], n - 1, records, res);
}
// 表示下一次重新计算 n 数之和, 将向量元素重置
if n as usize == record_len {
for j in 1..record_len {
records[j] = i32::MIN;
}
}
i += 1;
}
}
/// 计算两数之和
fn two_sum(nums: &Vec<i32>, start: usize, end: usize, target: i32) -> Vec<Vec<i32>> {
let mut results: Vec<Vec<i32>> = Vec::new();
let (mut left, mut right) = (start, end);
while left < right {
let left_val = nums[left];
let right_val = nums[right];
let sum = left_val + right_val;
// 如果相等,将元素放入集合
if sum == target {
let mut list = Vec::new();
list.push(left_val);
list.push(right_val);
results.push(list);
// 如果元素相同指针后移
while left < right && nums[left] == left_val {
left += 1;
}
// 如果元素相同指针前移
while left < right && nums[right] == right_val {
right -= 1;
}
continue;
}
// 和小于目标值,从右边寻找下一个较大的值
if sum < target {
left += 1;
continue;
}
// 和大于目标值,从左边寻找下一个较小的值
right -= 1;
}
results
}
}
#[test]
fn test_four_sum() {
let nums: Vec<i32> = vec![1, 0, -1, 0, -2, 2];
let target = 0;
println!("nums = {:?}, target = {}", nums.clone(), target);
let sum = Solution::four_sum(nums, target);
println!("{:?}", sum);
}
运行结果:
nums = [1, 0, -1, 0, -2, 2], target = 0
[[-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1]]
Go
var res [][]int
func fourSum(nums []int, target int) [][]int {
res = make([][]int, 0)
sort.Ints(nums)
// 记录当前第 n 个数
records := make([]int, 4)
for i := 0; i < len(records); i++ {
records[i] = math.MinInt32
}
nSum(nums, 0, len(nums) - 1, target, 4, records)
return res
}
// 计算 n 个数之和
func nSum(nums []int, start, end, target, n int, records []int) {
recordLen := len(records)
if n <= 2 {
// 计算两数之和
results := twoSum(nums, start, end, target)
// 存在两数之和等于目标值
if len(results) > 0 {
// 循环遍历结果, 将结果添加到最后的结果中
for i := 0; i < len(results);i++ {
records[recordLen - 2] = results[i][0]
records[recordLen - 1] = results[i][1]
res = append(res, append([]int{}, records...))
}
}
return
}
i := start
for ; i <= end; i++ {
// 如果元素重复, 跳过
if nums[i] == records[recordLen - n] {
continue
}
// 修改当前第 n 个数
records[recordLen - n] = nums[i]
if i + 1 < end {
// 继续计算第 n - 1 个数之和
nSum(nums, i + 1, end, target - nums[i], n - 1, records)
}
// 表示下一次重新计算 n 数之和, 将数组元素重置
if n == recordLen {
for j := 1; j < recordLen; j++ {
records[j] = math.MinInt32
}
}
}
}
// 计算两数之和
func twoSum(nums []int, start, end, target int) [][]int {
var results [][]int
left, right := start, end
sum := 0
for left < right {
leftVal := nums[left]
rightVal := nums[right]
sum = leftVal + rightVal
// 如果相等,将元素放入切片
if sum == target {
results = append(results, []int{leftVal, rightVal})
// 如果元素相同指针后移
for left < right && nums[left] == leftVal {
left++
}
// 如果元素相同指针前移
for left < right && nums[right] == rightVal {
right--
}
continue
}
// 和小于目标值,从右边寻找下一个较大的值
if sum < target {
left++
continue
}
// 和大于目标值,从左边寻找下一个较小的值
right--
}
return results
}
func TestFourSum(t *testing.T) {
nums := []int{1, 0, -1, 0, -2, 2}
target := 0
t.Logf("nums = %v, target = %v\n", nums, target)
sum := fourSum(nums, target)
t.Log(sum)
}
运行结果:
nums = [1 0 -1 0 -2 2], target = 0
[[-2 -1 1 2] [-2 0 0 2] [-1 0 0 1]]
Java
public class Main {
public static List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
int[] records = new int[4];
Arrays.fill(records, Integer.MIN_VALUE);
nSum(nums, 0, nums.length - 1, target, 4, records, res);
return res;
}
/**
* 计算 n 个数之和
*/
public static void nSum(int[] nums, int start, int end, int target, int n, int[] records, List<List<Integer>> res) {
int recordLen = records.length;
if (n <= 2) {
// 计算两数之和
List<List<Integer>> results = twoSum(nums, start, end, target);
// 存在两数之和等于目标值
if (results.size() > 0) {
// 循环遍历结果, 将结果添加到最后的结果中
for (List<Integer> result : results) {
records[recordLen - 2] = result.get(0);
records[recordLen - 1] = result.get(1);
List<Integer> list = new ArrayList<>();
for (int record : records) {
list.add(record);
}
res.add(list);
}
}
return;
}
int i = start;
for (; i < end; i++) {
// 如果元素重复, 跳过
if (nums[i] == records[recordLen - n]) {
continue;
}
// 修改当前第 n 个数
records[recordLen - n] = nums[i];
if (i + 1 < end) {
// 继续计算第 n - 1 个数之和
nSum(nums, i + 1, end, target - nums[i], n - 1, records, res);
}
// 表示下一次重新计算 n 数之和, 将数组元素重置
if (n == recordLen) {
for (int j = 1; j < recordLen; j++) {
records[j] = Integer.MIN_VALUE;
}
}
}
}
/**
* 计算两数之和
*/
public static List<List<Integer>> twoSum(int[] nums, int start, int end, int target) {
List<List<Integer>> results = new ArrayList<>();
int left = start, right = end;
int sum;
while (left < right) {
int leftVal = nums[left];
int rightVal = nums[right];
sum = leftVal + rightVal;
// 如果相等,将元素放入集合
if (sum == target) {
List<Integer> list = new ArrayList<>();
list.add(leftVal);
list.add(rightVal);
results.add(list);
// 如果元素相同指针后移
while (left < right && nums[left] == leftVal) {
left++;
}
// 如果元素相同指针前移
while (left < right && nums[right] == rightVal) {
right--;
}
continue;
}
// 和小于目标值,从右边寻找下一个较大的值
if (sum < target) {
left++;
continue;
}
// 和大于目标值,从左边寻找下一个较小的值
right--;
}
return results;
}
public static void main(String[] args) {
int[] nums = new int[]{1, 0, -1, 0, -2, 2};
int target = 0;
System.out.printf("nums = %s, target = %d\n", Arrays.toString(nums), target);
List<List<Integer>> results = fourSum(nums, target);
StringBuilder sb = new StringBuilder("[");
for (List<Integer> ints : results) {
sb.append("[");
for (Integer in : ints) {
sb.append(in).append(", ");
}
sb.deleteCharAt(sb.length() - 1);
sb.deleteCharAt(sb.length() - 1);
sb.append("]");
sb.append(", ");
}
sb.deleteCharAt(sb.length() - 1);
sb.deleteCharAt(sb.length() - 1);
sb.append("]");
System.out.println(sb.toString());
}
}
运行结果:
nums = [1, 0, -1, 0, -2, 2], target = 0
[[-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1]]