注:本文总结自 labuladong
twoSum 问题
- 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出能够凑出目标值 target 的两个元素的值(注意:不是下标)。
public int[] twoSum(int[] nums, int target) {
Arrays.sort(nums);
int low = 0, high = nums.length - 1;
while(low < high){
int sum = nums[low] + nums[high];
if(sum < target){
low++;
}else if(sum > target){
high--;
}else{
return new int[]{
nums[low], nums[high]};
}
}
return new int[0];
}
泛化的 twoSum 问题
- 如果 nums 中有多对元素之和都等于 target,请你的算法返回所有和为 target 的元素对,并且其中不能出现重复的二元组。
- 比如输入:nums = { 1, 3, 1, 2, 2, 3 },那么算法返回的结果就是:[[1, 3], [2, 2]]
- 对于修改后的问题,关键难点是现在可能有多个和为 target 的数对,还不能重复。比如上述例子中 [1, 3] 和 [3, 1] 虽然都符合要求,但是重复了,只能算一次。
- 出问题的地方在于:当 sum == target 时,在给 res 加入一次结果后,low 和 high 不应该只是改变 1,而是应该跳过所有重复的元素。
public static List<List<Integer>> twoSumTarget(int[] nums, int target){
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
int low = 0, high = nums.length - 1;
while(low < high){
int sum = nums[low] + nums[high];
int left = nums[low], right = nums[high];
if(sum < target){
while(low < high && nums[low] == left){
low++;
}
}else if(sum > target){
while(low < high && nums[high] == right){
high--;
}
}else{
List<Integer> list = new ArrayList<>();
list.add(nums[low]);
list.add(nums[high]);
res.add(new ArrayList<>(list));
while(low < high && nums[low] == left){
low++;
}
while(low < high && nums[high] == right){
high--;
}
}
}
return res;
}
- 时间复杂度:排序的时间复杂度是 O ( N l o g N ) O(NlogN) O(NlogN),双指针操作的部分虽然有很多 while 循环,但是时间复杂度还是 O ( N ) O(N) O(N)。所以这个函数的时间复杂度是 O ( N l o g N ) O(NlogN) O(NlogN)
3Sum 问题
- 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a, b, c,使得 a + b + c = target ?请你找出所有满足条件且不重复的三元组。
- 比如:
输入:nums = [-1,0,1,2,-1,-4], target = 0
输出:[ [-1,-1,2], [-1,0,1] ] - 思路:在确定了三元组中的第一个数字之后,我们需要找的就是剩下的两个和为 target - nums[i] 的数字,而这就是 twoSumTarget 函数所解决的问题。另外,三元组也可能会出现重复,比如当 nums = [1, 1, 1, 2, 3],target = 6 时,结果就会重复。关键点在于,不能让第一个数字重复,至于后面的两个数,twoSumTarget 函数会保证他们不重复。 因此我们需要在代码中用一个 while 循环来保证每个三元组中的第一个元素不重复。
class Solution {
public List<List<Integer>> threeSumTarget(int[] nums, int target) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
// 枚举三元组中的第一个数
for(int i = 0; i < nums.length; i++){
List<List<Integer>> temp = twoSumTarget(nums, target - nums[i], i + 1);
// 如果存在满足条件的二元组,那么再加上 nums[i] 就是结果三元组
for(List<Integer> t : temp){
t.add(nums[i]);
res.add(t);
}
// 跳过第一个数字重复的情况,否则会出现重复的结果。
// 比如:nums = [-4, -1, -1, 0, 1, 2], target = 0。我们选了一次 -1 作为三元组的第一个元素后,下一次再选第一个元素时,第二个 -1 就需要跳过,否则就会出现重复结果
while(i < nums.length - 1 && nums[i] == nums[i + 1]){
i++;
}
}
return res;
}
// 返回所有两数之和是 target 的元素对,且结果中不包含重复的二元组
public List<List<Integer>> twoSumTarget(int[] nums, int target, int start){
List<List<Integer>> res = new ArrayList<>();
int low = start, high = nums.length - 1;
while(low < high){
int sum = nums[low] + nums[high];
int left = nums[low], right = nums[high];
if(sum < target){
while(low < high && nums[low] == left){
low++;
}
}else if(sum > target){
while(low < high && nums[high] == right){
high--;
}
}else{
List<Integer> list = new ArrayList<>();
list.add(nums[low]);
list.add(nums[high]);
res.add(new ArrayList(list));
while(low < high && nums[low] == left){
low++;
}
while(low < high && nums[high] == right){
high--;
}
}
}
return res;
}
}
- 时间复杂度:排序的时间复杂度是 O ( N l o g N ) O(NlogN) O(NlogN),twoSumTarget 函数中的双指针操作为 O ( N ) O(N) O(N),threeSumTarget 函数在 for 循环中调用 twoSumTarget 函数,所以总的时间复杂度就是 O ( N l o g N + N 2 ) = O ( N 2 ) O( NlogN + N^2 ) = O (N^2) O(NlogN+N2)=O(N2)
4Sum 问题
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for(int i = 0; i < nums.length; i++){
List<List<Integer>> temp = threeSum(nums, target - nums[i], i + 1);
// 如果存在满足条件的三元组,那么再加上 nums[i] 就是结果四元组
for(List<Integer> t : temp){
t.add(nums[i]);
res.add(t);
}
while(i < nums.length - 1 && nums[i] == nums[i + 1]){
i++;
}
}
return res;
}
public List<List<Integer>> threeSum(int[] nums, int target, int start) {
List<List<Integer>> res = new ArrayList<>();
// 枚举三元组中的第一个数
for(int i = start; i < nums.length; i++){
List<List<Integer>> temp = twoSumTarget(nums, target - nums[i], i + 1);
// 如果存在满足条件的二元组,那么再加上 nums[i] 就是结果三元组
for(List<Integer> t : temp){
t.add(nums[i]);
res.add(t);
}
// 跳过数字重复的情况,否则会出现重复的结果。
// 比如:nums = [-4, -1, -1, 0, 1, 2], target = 0。我们选了一次 -1 作为三元组的第一个元素后,下一次再选第一个元素时,第二个 -1 就需要跳过,否则就会出现重复结果
while(i < nums.length - 1 && nums[i] == nums[i + 1]){
i++;
}
}
return res;
}
// 返回所有两数之和是 target 的元素对,且结果中没有出现重复
public List<List<Integer>> twoSumTarget(int[] nums, int target, int start){
List<List<Integer>> res = new ArrayList<>();
int low = start, high = nums.length - 1;
while(low < high){
int sum = nums[low] + nums[high];
int left = nums[low], right = nums[high];
if(sum < target){
while(low < high && nums[low] == left){
low++;
}
}else if(sum > target){
while(low < high && nums[high] == right){
high--;
}
}else{
List<Integer> list = new ArrayList<>();
list.add(nums[low]);
list.add(nums[high]);
res.add(new ArrayList(list));
while(low < high && nums[low] == left){
low++;
}
while(low < high && nums[high] == right){
high--;
}
}
}
return res;
}
}
- 时间复杂度:排序的时间复杂度是 O ( N l o g N ) O(NlogN) O(NlogN),fourSumTarget 函数在 for 循环中调用 threeSumTarget 函数,所以总的时间复杂度就是 O ( N l o g N + N 3 ) = O ( N 3 ) O( NlogN + N^3 ) = O (N^3) O(NlogN+N3)=O(N3)