x数之和
当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法;或者通过哈希表判断是否有目标元素存在
两数之和 II - 输入有序数组【LC167】
给定一个已按照 非递减顺序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。
函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length 。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/array-and-string/cnkjg/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
-
我的题解
-
暴力
- 2021/11/15
class Solution { public int[] twoSum(int[] numbers, int target) { int[] index = {0,0}; for(int i =0;i<numbers.length;i++){ for(int j=i+1;j<numbers.length;j++){ if(numbers[i]+numbers[j] == target){ index[0] = i+1; index[1] = j+1; } } } return index; } }
-
-
复杂度
- 时间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n是数组的长度。
- 空间复杂度: O ( 1 ) O(1) O(1)
双指针
2022/10/18
由于数组从小到大排列, 设置双指针分别指向数组的首部(left)和尾部(right)
-
若首部尾部相加等于目标值,返回结果集
-
若首部尾部相加小于目标值,右移left,使和变大
-
若首部尾部相加大于目标值,左移right,使和减小
-
代码
class Solution { public int[] twoSum(int[] numbers, int target) { int[] res = new int[2]; int len = numbers.length; int left = 0; int right = len-1; while(left < right){ if (numbers[left] + numbers[right] == target){ res[0] = left; res[1] = right; return res; }else if(numbers[left] + numbers[right] > target){ right--; }else { left++; } } return res; } }
-
复杂度
- 时间复杂度:O(n),其中 n是数组的长度。两个指针移动的总次数最多为 n次。
- 空间复杂度:O(1)
二分查找
2023/7/8
依次遍历数组中的元素,然后使用二分法查找数组中是否存在元素的数值为targe-numbers[i]
- 若存在,则返回结果
- 若不存在,则继续遍历
class Solution {
public int[] twoSum(int[] numbers, int target) {
int n = numbers.length;
for (int i = 0; i < n; i++){
int index = search(numbers, target - numbers[i], i + 1, n - 1);
if (index != -1){
return new int[]{i + 1, index + 1};
}
}
return null;
}
public int search(int[] nums, int target, int l, int r){
while (l <= r){
int mid = (l + r) >> 1;
if (nums[mid] == target){
return mid;
}else if (nums[mid] > target){
r = mid - 1;
}else{
l = mid + 1;
}
}
return -1;
}
}
- 复杂度
- 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
- 空间复杂度: O ( 1 ) O(1) O(1)
二分查找+双指针
class Solution {
public int[] twoSum(int[] numbers, int target) {
int i = 0, j = numbers.length - 1;
while (i < j) {
int m = (i + j) >>> 1;
if (numbers[i] + numbers[m] > target) {
j = m - 1;
} else if (numbers[m] + numbers[j] < target) {
i = m + 1;
} else if (numbers[i] + numbers[j] > target) {
j--;
} else if (numbers[i] + numbers[j] < target) {
i++;
} else {
return new int[]{i + 1, j + 1};
}
}
return new int[]{0, 0};
}
}
https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted/solution/liang-shu-zhi-he-ii-shu-ru-you-xu-shu-zu-by-leet-2/502315
- 复杂度
- 时间复杂度:最好情况 O ( l o g n ) O(logn) O(logn),最坏 O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
三数之和【LC15】
给你一个整数数组
nums
,判断是否存在三元组[nums[i], nums[j], nums[k]]
满足i != j
、i != k
且j != k
,同时还满足nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为
0
且不重复的三元组。**注意:**答案中不可以包含重复的三元组。
排序+双指针
-
思路
- 首先对数组进行排序,然后遍历数组,在确定
nums[i]
的情况下,在数组nums[i+1,n-1]
中使双指针法(前后指针),确定另外两个元素,判断这三个元素之和sum
与target
的大小:- 如果
s
u
m
>
t
a
r
g
e
t
sum>target
sum>target,左移左指针,使
sum
减小 - 如果
s
u
m
<
t
a
r
g
e
t
sum<target
sum<target,右移右指针,使
sum
增大 - 如果 s u m = = t a r g e t sum==target sum==target,添加三元组至结果集
- 如果
s
u
m
>
t
a
r
g
e
t
sum>target
sum>target,左移左指针,使
- 去重:
- 在找到符合条件的三元组后,移动指针
r、l
跳过相同元素,以便过滤掉重复的三元组 - 如果
nums[i]==nums[i-1]
, n u m s [ i ] nums[i] nums[i]的三元组是 n u m s [ i − 1 ] nums[i-1] nums[i−1]的子集,需要跳过 - 在遇到不符合条件的左右边界时也可以移动双指针去重,但是由于根据sum和target的大小关系,双指针也会被移动,所以此时是否去重不是必须的
- 在找到符合条件的三元组后,移动指针
- 首先对数组进行排序,然后遍历数组,在确定
-
实现
-
首先将数组排序;
-
然后有一层for循环, i i i从下表0的地方开始,同时定义下标left在i+1位置上,定义下标right在数组末尾的位置上
-
如果
nums[i]>0
,不可能存在三元组,直接退出循环,返回结果集 -
去重:如果
(i > 0 && nums[i] == nums[i - 1])
, n u m s [ i ] nums[i] nums[i]的三元组是 n u m s [ i − 1 ] nums[i-1] nums[i−1]的子集,需要跳过 -
移动left和right
-
如果
nums[i] + nums[left] + nums[right] > 0,
说明right下标应该向左移动去重:
while (left < right && nums[right] == nums[right + 1]) right--;
【非必须】 -
如果
nums[i] + nums[left] + nums[right] < 0,
说明left下标应该向右移动去重:
while (left < right && nums[left] == nums[left - 1]) left++;
【非必须】 -
如果
nums[i] + nums[left] + nums[right] == 0,
说明找到了三元组,双指针同时收缩,right--;left++
去重:
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
class Solution { public List<List<Integer>> threeSum(int[] nums) { List<List<Integer>> res = new ArrayList<>(); Arrays.sort(nums); for (int i = 0; i < nums.length - 2; i++){ if (nums[i] > 0){ return res; } if (i > 0 && nums[i] == nums[i-1] ){ continue; } int left = i + 1; int right = nums.length - 1; while (left < right){ int sum = nums[i] + nums[left] + nums[right]; if (sum > 0){ right--; // 非必须 while(left < right && nums[right] == nums[right+1]){ right--; } }else if (sum < 0){ // 非必须 left++; while (left < right && nums[left-1] == nums[left]){ left++; } }else{ res.add(Arrays.asList(nums[i],nums[left],nums[right])); while(left < right && nums[right] == nums[right-1]){ right--; } while (left < right && nums[left+1] == nums[left]){ left++; } right--; left++; } } } return res; } }
class Solution { public List<List<Integer>> threeSum(int[] nums) { Arrays.sort(nums); List<List<Integer>> res = new ArrayList<>(); int len = nums.length; int i = 0; while (i < len - 2){ twoSum(nums,i,res); int temp = nums[i]; while (i < len && temp == nums[i]){ i++; } } return res; } public void twoSum(int[] numbers, int i, List<List<Integer>> res ) { int len = numbers.length; int left = i + 1; int right = len - 1; while(left < right){ if ( numbers[i] + numbers[left] + numbers[right] == 0){ res.add(Arrays.asList(numbers[i],numbers[left],numbers[right])); int temp = numbers[left]; while (numbers[left] == temp && left < right){ left++; } }else if(numbers[i] + numbers[left] + numbers[right] > 0){ right--; }else { left++; } } } }
-
复杂度
- 时间复杂度: O ( n l o g n + n 2 ) O(nlogn+n^2) O(nlogn+n2),其中 n是数组的长度。排序所需的时间复杂度一般为 O ( n l o g n ) O(nlogn) O(nlogn),查找三元组的时间复杂度为 O ( n 2 ) O(n^2) O(n2),因此时间复杂度为 O ( n 2 ) O(n^2) O(n2)
- 空间复杂度: O ( 1 ) O(1) O(1)
-
-
最接近的三数之和【LC16】
给你一个长度为
n
的整数数组nums
和 一个目标值target
。请你从nums
中选出三个整数,使它们的和与target
最接近。返回这三个数的和。
假定每组输入只存在恰好一个解。
2023/1/14
-
思路:
- 搜索三数之和的过程与LC15相同:首先对数组进行排序,然后遍历数组,在确定
nums[i]
的情况下,在数组nums[i+1,n-1]
中使双指针法(前后指针),确定另外两个元素,判断这三个元素之和sum
与target
的大小:- 如果
s
u
m
>
t
a
r
g
e
t
sum>target
sum>target,左移左指针,使
sum
减小 - 如果
s
u
m
<
t
a
r
g
e
t
sum<target
sum<target,右移右指针,使
sum
增大 - 如果 s u m = = t a r g e t sum==target sum==target,添加三元组至结果集
- 如果
s
u
m
>
t
a
r
g
e
t
sum>target
sum>target,左移左指针,使
- 不同之处为更新结果的条件变为如果
sum
更接近target
那么更新sum
。如果sum==target
,那么可以直接返回target
。本题不去重也不影响结果 - 本题只需要找到最接近的和,因此去重不是必须的
- 搜索三数之和的过程与LC15相同:首先对数组进行排序,然后遍历数组,在确定
-
实现
// 2023/7/10 class Solution { public int threeSumClosest(int[] nums, int target) { Arrays.sort(nums); int n = nums.length ; int res = Integer.MAX_VALUE; for (int i = 0; i < n - 2; i++){ int l = i + 1, r = n - 1; if (i > 0 && nums[i] == nums[i - 1]){ continue; } while (l < r){ int sum = nums[i] + nums[l] + nums[r]; if (Math.abs(sum - target) < Math.abs(res - target)){ res = sum; } if (sum == target){ return target; }else if(sum > target){ r--; }else{ l++; } } } return res; } }
-
复杂度
- 时间复杂度: O ( n l o g n + n 2 ) O(nlogn+n^2) O(nlogn+n2),其中 n是数组的长度。排序所需的时间复杂度一般为 O ( n l o g n ) O(nlogn) O(nlogn),查找三元组的时间复杂度为 O ( n 2 ) O(n^2) O(n2),因此时间复杂度为 O ( n 2 ) O(n^2) O(n2)
- 空间复杂度: O ( 1 ) O(1) O(1)
-
较小的三数之和【LC259】
给定一个长度为
n
的整数数组和一个目标值target
,寻找能够使条件nums[i] + nums[j] + nums[k] < target
成立的三元组i, j, k
个数(0 <= i < j < k < n
)。
2023/1/14
双指针
-
思路:首先对数组排序,然后固定 n u m s [ i ] nums[i] nums[i],使用双指针在 n u m s [ i + 1 , n − 1 ] nums[i+1,n-1] nums[i+1,n−1]内搜索左指针最小右指针最大的二元组,。左指针起始位置为 i + 1 i+1 i+1,右指针起始位置为 n − 1 n-1 n−1,假设三数之和为
sum
- 如果
s
u
m
>
t
a
r
g
e
t
sum>target
sum>target,那么需要左移右指针,减小
sum
- 如果
s
u
m
<
t
a
r
g
e
t
sum<target
sum<target,那么此时的二元组满足要求,并且固定
i
和l
时,右指针r
的可取位置为 [ l + 1 , r ] [l+1,r] [l+1,r],此时的三元组之和均小于target
,对结果的贡献为 r − l r-l r−l。然后右移左指针,寻找新的二元组
本题不要求去重,因为统计的是下标的个数
- 如果
s
u
m
>
t
a
r
g
e
t
sum>target
sum>target,那么需要左移右指针,减小
-
实现
class Solution { public int threeSumSmaller(int[] nums, int target) { int count = 0, n = nums.length; Arrays.sort(nums); for (int i = 0; i < n - 2; i++){ int l = i + 1, r = n - 1; while (l < r){ int sum = nums[i] + nums[l] + nums[r]; if (sum < target){ count += r - l; l++; }else{ r--; } } } return count; } }
-
复杂度
- 时间复杂度: O ( n l o g n + n 2 ) O(nlogn+n^2) O(nlogn+n2),其中 n是数组的长度。排序所需的时间复杂度一般为 O ( n l o g n ) O(nlogn) O(nlogn),查找三元组的时间复杂度为 O ( n 2 ) O(n^2) O(n2),因此时间复杂度为 O ( n 2 ) O(n^2) O(n2)
- 空间复杂度: O ( 1 ) O(1) O(1)
-
二分查找
-
思路:我们从小到大枚举 i i i 和 j j j,规定 i < j i<j i<j,随后在数组上二分查找,找出最大的满足 n u m s [ i ] + n u m s [ j ] + n u m s [ k ] < t a r g e t nums[i]+nums[j]+nums[k]<target nums[i]+nums[j]+nums[k]<target 的 k k k,那么对于当前的 i i i和 j j j,有 k − j k−j k−j 对三元组是满足要求的。
-
实现
public int threeSumSmaller(int[] nums, int target) { Arrays.sort(nums); int sum = 0; for (int i = 0; i < nums.length - 2; i++) { sum += twoSumSmaller(nums, i + 1, target - nums[i]); } return sum; } private int twoSumSmaller(int[] nums, int startIndex, int target) { int sum = 0; for (int i = startIndex; i < nums.length - 1; i++) { int j = binarySearch(nums, i, target - nums[i]); sum += j - i; } return sum; } private int binarySearch(int[] nums, int startIndex, int target) { int left = startIndex; int right = nums.length - 1; while (left < right) { int mid = (left + right + 1) / 2; if (nums[mid] < target) { left = mid; } else { right = mid - 1; } } return left; } 作者:力扣 (LeetCode) 链接:https://leetcode.cn/problems/3sum-smaller/solutions/11601/jiao-xiao-de-san-shu-zhi-he-by-leetcode/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
-
复杂度
- 时间复杂度: O ( n l o g n + n 2 l o g n ) O(nlogn+n^2logn) O(nlogn+n2logn),其中 n是数组的长度。排序所需的时间复杂度一般为 O ( n l o g n ) O(nlogn) O(nlogn),查找三元组的时间复杂度为 O ( n 2 l o g n ) O(n^2logn) O(n2logn)
- 空间复杂度: O ( 1 ) O(1) O(1)
-
四数之和【LC18】
给你一个由
n
个整数组成的数组nums
,和一个目标值target
。请你找出并返回满足下述全部条件且不重复的四元组[nums[a], nums[b], nums[c], nums[d]]
(若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a
、b
、c
和d
互不相同nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
排序+双指针
-
思路
类似三数之和,先将数组排序,然后固定两个数 n u m s [ a ] , n u m s [ b ] nums[a],nums[b] nums[a],nums[b],然后在区间 [ b + 1 , n − 1 ] [b+1,n-1] [b+1,n−1]使用双指针搜索是否有两数之和为 t a r g e t − n u m s [ a ] − n u m s [ b ] target-nums[a]-nums[b] target−nums[a]−nums[b],如果有则记录答案;否则根据大小,右移左指针或者左移右指针。
- 注意去重以及溢出
- 优化:
- 判断最小四元组是否大于target,是则break
- 判断最大四元组是否小于target,实则continue
-
实现
class Solution { public List<List<Integer>> fourSum(int[] nums, int target) { List<List<Integer>> res = new ArrayList<>(); int n = nums.length; Arrays.sort(nums); for (int i = 0; i < n - 3; i++){ if (i > 0 && nums[i] == nums[i - 1]) continue; long x = nums[i]; if (x + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) break; if (x + nums[n - 1] + nums[n - 2] + nums[n - 3] < target) continue; for (int j = i + 1; j < n - 2; j++){ if (j > i + 1 && nums[j] == nums[j - 1]) continue; long sum2 = nums[i] + nums[j]; int l = j + 1, r = n - 1; long num = target - sum2; while (l < r){ if (nums[l] + nums[r] == num){ res.add(Arrays.asList(nums[i], nums[j], nums[l], nums[r])); l++; while (l < n && nums[l - 1] == nums[l]){ l++; } r--; while (r > l && nums[r + 1] == nums[r]){ r--; } }else if (nums[l] + nums[r] > num){ r--; }else{ l++; } } } } return res; } }
-
复杂度
- 时间复杂度: O ( n l o g n + n 2 ) O(nlogn+n^2) O(nlogn+n2),其中 n是数组的长度。排序所需的时间复杂度一般为 O ( n l o g n ) O(nlogn) O(nlogn),查找三元组的时间复杂度为 O ( n 3 ) O(n^3) O(n3),因此时间复杂度为 O ( n 3 ) O(n^3) O(n3)
- 空间复杂度: O ( 1 ) O(1) O(1)
-
两数之和【LC1】
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
哈希表
-
题解
注意:先在map中寻找是否存在
target-num[i]
,若存在则返回下标,若不存在再将nums[i],i
放入map中,避免返回相同坐标或者坐标覆盖的情况class Solution { public int[] twoSum(int[] nums, int target) { int[] res = new int[2]; if(nums == null || nums.length == 0){ return res; } Map<Integer, Integer> map = new HashMap<>(); for(int i = 0; i < nums.length; i++){ int temp = target - nums[i]; if(map.containsKey(temp)){ res[1] = i; res[0] = map.get(temp); } map.put(nums[i], i); } return res; } }
四数之和【LC454】
给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/4sum-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
哈希表
-
题解
2022/4/20
- 首先求两数之和后,将nums1[i]+nums2[j],nums3[k]和nums4[l]中的数字对应相加,将和以及出现的次数依次放入map1和map2中
- 然后遍历map1,在map2中寻找是否存在-nums1[i]-nums2[j],如果存在则将对应的value值相乘后累加至res
class Solution { public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) { int len = nums1.length; Map<Integer,Integer> sum1 = new HashMap<>(); Map<Integer,Integer> sum2 = new HashMap<>(); for (int i = 0; i < len; i++){ for (int j = 0; j < len; j++){ sum1.put(nums1[i] + nums2[j], sum1.getOrDefault(nums1[i] + nums2[j],0) + 1); sum2.put(nums3[i] + nums4[j], sum2.getOrDefault(nums3[i] + nums4[j],0) + 1); } } Set<Map.Entry<Integer,Integer>> set1 = sum1.entrySet(); int res = 0; for (Map.Entry<Integer,Integer> node:set1 ){ int target = -1 * node.getKey().intValue(); if(sum2.containsKey(target)){ res += node.getValue().intValue() * sum2.get(target); } } return res; } }
-
改进
只用一个map记录nums1[i]+nums2[j],多使用一个双重for循环在map中寻找nums3[k]和nums4[l]
class Solution { public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) { int len = nums1.length; Map<Integer,Integer> sum1 = new HashMap<>(); for (int i = 0; i < len; i++){ for (int j = 0; j < len; j++){ sum1.put(nums1[i] + nums2[j], sum1.getOrDefault(nums1[i] + nums2[j],0) + 1); } } int res = 0; for (int i = 0; i < len; i++){ for (int j = 0; j < len; j++){ int target = -nums3[i] - nums4[j]; if(sum1.containsKey(target)){ res += sum1.get(target); } } } return res; } }