leetcode数和问题:两数之和、三数之和、四数之和
-
两数之和:HashMap或者双重循环
-
有序数组的两数之和:双指针或者二分查找
-
三数之和:排序+双指针,单重循环嵌套双指针
-
四数之和:排序+双指针,双重循环嵌套双指针
-
四个数组内的四数之和:分组+HashMap
1. 两数之和
1.1 题目描述
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
提示:
2 <= nums.length <= 103
-109 <= nums[i] <= 109
-109 <= target <= 109
- 只会存在一个有效答案
1.2 解题思路一
双重循环,时间复杂度 $O(n^2) $,空间复杂度$O(1) $。
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] result = new int[2];
i:for(int i=0;i<nums.length-1;i++) {
for(int j=i+1;j<nums.length;j++) {
if(nums[i] + nums[j] == target){
result[0] = i;
result[1] = j;
break i;
}
}
}
return result;
}
}
1.3 解题思路二
利用HashMap,空间换时间,时间复杂度$O(n)$ ,空间复杂度$O(n)$ 。
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] result = new int[2];
Map<Integer, Integer> hashMap = new LinkedHashMap<Integer, Integer>();
for(int i=0;i<nums.length;i++) {
//target - nums[i'] == num[i]
//说明这两个位置上的数字之和等于target
if(hashMap.containsKey(nums[i])){
//通过键找值,找到target - nums[i']对应的下标
result[0] = hashMap.get(nums[i]);
result[1] = i;
break;
}
//键:target-nums[i],值:下标
hashMap.put(target-nums[i], i);
}
return result;
}
}
167. 两数之和 II - 输入有序数组
167.1 题目描述
给定一个已按照 升序排列 的整数数组 numbers
,请你从数组中找出两个数满足相加之和等于目标数 target
。
函数应该以长度为 2
的整数数组的形式返回这两个数的下标值。numbers
的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length
。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例 1:
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
示例 2:
输入:numbers = [2,3,4], target = 6
输出:[1,3]
示例 3:
输入:numbers = [-1,0], target = -1
输出:[1,2]
提示:
2 <= numbers.length <= 3 * 104
-1000 <= numbers[i] <= 1000
numbers
按 递增顺序 排列-1000 <= target <= 1000
- 仅存在一个有效答案
167.2 解题思路
思路一:双指针,时间复杂度$O(n)$,空间复杂度$O(1)$
思路二:二分查找,时间复杂度$O(nlogn)$,空间复杂度$O(1)$
class Solution {
public int[] twoSum(int[] numbers, int target) {
int[] result = new int[2];
for(int i=0,j=numbers.length-1;i<j;) {
if(numbers[i] + numbers[j] == target) {
result[0] = i+1;
result[1] = j+1;
break;
}else if(numbers[i] + numbers[j] > target) {
j--;
}else {
i++;
}
}
return result;
}
}
15. 三数之和
15.1 题目描述
给你一个包含 n
个整数的数组 nums
,判断 nums
中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
示例 2:
输入:nums = []
输出:[]
示例 3:
输入:nums = [0]
输出:[]
提示:
0 <= nums.length <= 3000
-105 <= nums[i] <= 105
15.2 解题思路
三重循环使用双指针方式转化成双重循环
具体思路:
-
将数组按从大到小的顺序进行排序:
Arrays.sort(nums);
,时间复杂度$O(nlogn)$。 -
判断特例:
- 数组大小小于3,直接返回空链表
- 排序后数组的首位元素(循环中 i 指向的元素)大于0,直接返回结果
-
使用三个指针,分别为i、j、k,双重循环时间复杂度$O(n^2)$
- 固定 i 不变,j 和 k 分别指向下标 i 之后的数组的头和尾
- 如果三指针指向的数组元素和大于0,则 k--
- 如果三指针指向的数组元素和小于0,则 j++
- 如果三指针指向的数组元素和等于0,则在返回链表中添加对应的数组元素
-
题目中的难点:返回的三元组结果不能重复
- 为了避免重复,我们首先要保证最外层循环中,i 指向的元素是不重复的,所以每次循环前要检查目前 i 指向的数组元素是否等于 i 上次指向的数组元素,如果重复就 i++
- 在内层循环中,找到和等于0的三元素之后,继续内层循环,此时要确保 j 和 k 指向的元素和上次指向的元素不同
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> linkedList = new LinkedList<>();
Arrays.sort(nums);
if(nums.length<3) {
return linkedList;
}
for(int i=0;i<nums.length-2;i++) {
if(nums[i]>0)
break;
while(i>0 && i<nums.length-2 && nums[i]==nums[i-1]) {
i++;
}
int j=i+1,k=nums.length-1;
while(j<k) {
if(nums[i]+nums[j]+nums[k]>0) {
k--;
}else if(nums[i]+nums[j]+nums[k]<0){
j++;
}else {
//双重链表添加元素的方式
linkedList.add(new LinkedList<>(Arrays.asList(nums[i],nums[j],nums[k])));
k--;
j++;
while(j<k && nums[j]==nums[j-1]){
j++;
}
while(j<k && nums[k]==nums[k+1]){
k--;
}
}
}
}
return linkedList;
}
}
16. 最接近的三数之和
16.1 题目描述
给定一个包括 n 个整数的数组 nums
和 一个目标值 target
。找出 nums
中的三个整数,使得它们的和与 target
最接近。返回这三个数的和。假定每组输入只存在唯一答案。
示例:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
提示:
3 <= nums.length <= 10^3
-10^3 <= nums[i] <= 10^3
-10^4 <= target <= 10^4
16.2 解题思路
排序+双指针
时间复杂度$O(n^2)$ ,空间复杂度$O(logn)$
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
if(nums.length<3) {
return 0;
}
int result, mostClose = nums[0] + nums[1] + nums[2];
for(int i=0;i<nums.length-2;i++) {
for(int j=i+1,k=nums.length-1;j<k;) {
result = nums[i] + nums[j] +nums[k];
if(result>target) {
k--;
}else if(result<target) {
j++;
}else {
return result;
}
if(Math.abs(mostClose-target)>Math.abs(result-target)) {
mostClose = result;
}
}
}
return mostClose;
}
}
18. 四数之和
18.1 题目描述
给定一个包含 n 个整数的数组 nums
和一个目标值 target
,判断 nums
中是否存在四个元素 a,**b,c 和 d ,使得 a + b + c + d 的值与 target
相等?找出所有满足条件且不重复的四元组。
注意:答案中不可以包含重复的四元组。
示例 1:
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例 2:
输入:nums = [], target = 0
输出:[]
提示:
0 <= nums.length <= 200
-109 <= nums[i] <= 109
-109 <= target <= 109
18.2 解题思路
排序+双指针
双重循环
时间复杂度:$O(n^3)$,其中 $n$ 是数组的长度。排序的时间复杂度是$O(nlogn)$,枚举四元组的时间复杂度是
$O(n^3)$ ),因此总时间复杂度为:$O(n^3)+O(nlogn)$
空间复杂度:$O(logn)$,其中 $n$ 是数组的长度。空间复杂度主要取决于排序额外使用的空间。此外排序修改了输入数组nums
,实际情况中不一定允许,因此也可以看成使用了一个额外的数组存储了数组nums
的副本并排序,空间复杂度为$O(n)$。
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> list = new LinkedList<>();
if(nums.length < 4){
return list;
}
Arrays.sort(nums);
int result=0;
for(int i=0;i<nums.length-3;) {
for(int l=i+1;l<nums.length-2;) {
for(int j=l+1,k=nums.length-1;j<k;) {
result = nums[i] + nums[j] + nums[k] + nums[l];
if(result>target) {
k--;
}else if(result<target) {
j++;
}else {
list.add(new LinkedList<>(Arrays.asList(nums[i],nums[l],nums[j],nums[k])));
j++;
k--;
//检查重复元素
while(j<k && nums[j]==nums[j-1]) {
j++;
}
}
}
l++;
//检查重复元素
while(nums[l]==nums[l-1] && l<nums.length-2) {
l++;
}
}
i++;
//检查重复元素
while(nums[i]==nums[i-1] && i<nums.length-3) {
i++;
}
}
return list;
}
}
454. 四数相加 II
454.1 题目描述
给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l)
,使得 A[i] + B[j] + C[k] + D[l] = 0
。
为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。
例如:
输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]
输出:
2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0
454.2 解题思路
分组+哈希表
时间复杂度$O(n2)$,空间复杂度$O(n2)$
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
int n=nums1.length;
if(n == 0) {
return 0;
}
int count=0;
Map<Integer,Integer> hashMap = new LinkedHashMap<>();
for(int i=0;i<n;i++) {
for(int j=0;j<n;j++) {
//key:两数之和 value:该数出现的次数
if(hashMap.containsKey(nums1[i]+nums2[j])) {
hashMap.put(nums1[i]+nums2[j], hashMap.get(nums1[i]+nums2[j])+1);
}else {
hashMap.put(nums1[i]+nums2[j], 1);
}
}
}
for(int k=0;k<n;k++) {
for(int l=0;l<n;l++) {
if(hashMap.containsKey(-nums3[k]-nums4[l])) {
count += hashMap.get(-nums3[k]-nums4[l]);
}
}
}
return count;
}
}