两数之和 三数之和
167. 两数之和II - 输入有序数组
给你一个下标从 1 开始的整数数组 numbers
,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target
的两个数。如果设这两个数分别是 numbers[index1]
和 numbers[index2]
,则 1 <= index1 < index2 <= numbers.length
。
以长度为 2 的整数数组 [index1, index2]
的形式返回这两个整数的下标 index1
和 index2
。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
示例 1:
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
示例 2:
输入:numbers = [2,3,4], target = 6
输出:[1,3]
解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。
示例 3:
输入:numbers = [-1,0], target = -1
输出:[1,2]
解释:-1 与 0 之和等于目标数 -1 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
提示:
2 <= numbers.length <= 3 * 104
-1000 <= numbers[i] <= 1000
numbers
按 非递减顺序 排列-1000 <= target <= 1000
- 仅存在一个有效答案
题目分析
本题可以使用暴力解法,也就是直接枚举,使用双层循环,那么此时的时间复杂度就是O(n^2),那有没有更加优化的算法呢?当然是有的。
本题中数组元素都是排好序的,暴力解法中并没有利用这一特点,那我们在优化中是不是可以考虑使用这个特点呢?
例如numbers = [2,7,11,15], target = 9
,我们可以看到2 + 15
已然是大于9的,是不是就意味着7 + 15
乃至11 + 15
都是大于9的呢,所以我们大可以将15排除在我们的答案之外,同样的道理,若两数相加小于target
,我们便可以将较小的数排除在我们的答案之外,根据这个,我们可以用双指针法写出这道题。
LeetCode代码
class Solution {
public int[] twoSum(int[] numbers, int target) {
int l = 0, r = numbers.length - 1; // 设立左右两个指针
while(l < r){
if(numbers[l] + numbers[r] > target){
r --; // 若两数字之和大于target,就排除较大的数
}else if(numbers[l] + numbers[r] < target){
l ++; // 若两数字之和小于target,就排除较小的数
}else return new int[] {l + 1, r + 1};
}
return new int[]{};
}
}
15. 三数之和
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为 0
且不重复的三元组。
**注意:**答案中不可以包含重复的三元组。
示例 s1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000
-105 <= nums[i] <= 105
题目分析
其实这个题就是我们上面题增加了一个加数,整体逻辑是差不多的,由于输出的顺序和三元组的顺序并不重要,我们便可以直接对数组进行重新排序,又题中提到i != j
、i != k
且 j != k
,我们可以使i < j < k
,而后利用双指针的算法进行代码的编写。
题目中还提到,答案中不可以包含重复的三元组,那么我们如何进行去重呢?很简单,只需要在移动指针的时候将当前指针对应的数组元素和移动前对应的数组元素进行对比,如果相同则继续移动指针便可。
LeetCode 代码
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> ans = new ArrayList<>();
int n = nums.length;
for(int i = 0; i < n - 2; i ++){
if(i > 0 && nums[i] == nums[i - 1]) continue; // 对i去重
int j = i + 1;
int k = n - 1;
while(j < k){
int s = nums[i] + nums[j] + nums[k];
// 和上一题一样的双指针
if(s > 0) k --;
else if(s < 0) j ++;
else{
List<Integer> r = new ArrayList<>();
r.add(nums[i]);
r.add(nums[j]);
r.add(nums[k]);
ans.add(r);
j ++;
while(j < k && nums[j] == nums[j - 1]) j ++;
k --;
while(j < k && nums[k] == nums[k + 1]) k --;
}
}
}
return ans;
}
}
优化
当然,还可以在原有代码基础上进行一部分优化;
- 如果从
nums[i]
开始的三个数组元素相加的值大于0,也就是当前最小的三个元素相加都大于0,那么它们后面的元素相加的结果必然大于0,到这里也就没有必要再执行下去了。 - 如果
nums[i]
和数组中最后两个元素相加的结果小于0,那此时的nums[i]
和其他别的元素相加的结果也必定小于0,此时我们可以直接continue
,不执行下面复杂的代码了。 - 如果数组中最后三个元素相加的值小于0,那么数组中其他元素相加也必定小于0,则可以直接返回空的数组。
- 如果数组中前三个元素相加的值大于0,那么数组中其他元素相加也必定大于0,则可以直接返回空的数组。
优化后的代码
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> ans = new ArrayList<>();
int n = nums.length;
if(nums[n - 1] + nums[n - 2] + nums[n - 3] < 0) return ans;
if(nums[0] + nums[1] + nums[2] > 0) return ans;
for(int i = 0; i < n - 2; i ++){
if(i > 0 && nums[i] == nums[i - 1]) continue;
if(nums[i] + nums[i + 1] + nums[i + 2] > 0) break;
if(nums[i] + nums[n - 1] + nums[n - 2] < 0) continue;
int j = i + 1;
int k = n - 1;
while(j < k){
int s = nums[i] + nums[j] + nums[k];
if(s > 0) k --;
else if(s < 0) j ++;
else{
List<Integer> r = new ArrayList<>();
r.add(nums[i]);
r.add(nums[j]);
r.add(nums[k]);
ans.add(r);
j ++;
while(j < k && nums[j] == nums[j - 1]) j ++;
k --;
while(j < k && nums[k] == nums[k + 1]) k --;
}
}
}
return ans;
}
}