第一部分:数组
15.三数之和(中等)
题目:给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入: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] 。
注意,输出的顺序和三元组的顺序并不重要。
第一种思路:
一开始就想用三层循环的做法,但是考虑到昨天刷的题,两层循环都时间超时了,这个三层就不用再考虑了。然后想到用指针,(先稍微分析一下题目,三数之和为0,也就是两数之和为第三个数的相反数),不过自己当时只是单纯地想遍历确定第三个数,然后另外两个指针遍历其他数(分别指向首元素和末尾元素),但是这时会有一个问题,如果这两个数不等于第三个数的相反数,那么这两个指针接下来要怎么移动呢,后来实在想不通(水平太低,算法任重道远啊)就小小地看了一眼官方的解答,没想到他也是用双指针的(指针真的很常用嘛),然后分析里就提到了,可以先对数组进行排序,后来才得知:在三数之和问题中,通常对数组进行排序是一个常见的做法,因为排序有助于简化问题,并且可以减少重复计算。
排序的优势包括:
提供有序的数据,使得在搜索和比较时更加高效。
有利于识别重复项,避免重复计算相同的组合。
有助于降低时间复杂度。
然而,值得一提的是,并非所有的算法都需要先对数组进行排序。有些方法也可以在不排序的情况下解决三数之和的问题。但是在大多数情况下,排序可以简化问题的解决方案,并且提高问题的解决效率。
在这之后问题就迎刃而解了。
用循环依次遍历第三个数,然后两个指针分别指向第三个数的后一位(会根据第三个数的遍历而改变)和数组的最后一位(每次循环开始时都是这个值),然后进行三种情况的判断:
q指针和p指针指向的元素如果等于第三个数的相反数就放入列表中,然后两个指针都向中间移动,因为两数之和等于一个特定数时,知道一个数,另一个数就唯一确定了,所以两个指针都要移动(避免重复)
两数之和小于第三个数的相反数(刚开始写的时候觉得不用相反数,后来想起来这第三个就是一个固定的数,怎么能莫名删除呢),因为数组是按照从小到大排序的,小于则需要将q指针向后移动,取个大点的值
两数之和大于第三个数的相反数,数组是按照从小到大排序的,大于则需要将p指针向前移动,取个小点的值
class Solution {
public static List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> list2 = new ArrayList<>();
Arrays.sort(nums);
int q = 0,p = nums.length - 1;
for(int i = 0; i < nums.length - 2; i++){
if(i > 0 && nums[i]==nums[i-1])
continue;
q = i + 1;
p = nums.length - 1;
while(q < p){
if(nums[q]+nums[p] == -nums[i]){
//list2.add(Arrays.asList(nums[i],nums[q],nums[p]))
List<Integer> list1 = new ArrayList<Integer>();
list1.add(nums[i]);
list1.add(nums[q]);
list1.add(nums[p]);
list2.add(list1);
while(q<p && nums[q]==nums[q+1]) q++;
while(q<p && nums[p]==nums[p-1]) p--;
q++;
p--;
}else if(nums[q]+nums[p] < -nums[i]){
q++;
}else if(nums[q]+nums[p] > -nums[i]){
p--;
}
}
}
return list2;
}
}
上面代码中的:
//list2.add(Arrays.asList(nums[i],nums[q],nums[p]))
List<Integer> list1 = new ArrayList<Integer>();
list1.add(nums[i]);
list1.add(nums[q]);
list1.add(nums[p]);
list2.add(list1);直接创建一个新的List并添加到结果集中,比使用了Arrays.asList方法来创建List,可能会引入一些性能开销。经过测试后发现确实如此,大概会减少百分之十的性能
第二种写法:
看了其他大佬的解答,发现大体思路基本和我自己的想法差不多,但是确实有精妙之处值得借鉴,击败数就是直观感受,下面是大佬的代码:
下面的代码在内层循环中使用了一个while循环来移动第三个指针,直到找到满足条件的组合或者指针重合。这种方式可以更快地找到符合条件的组合,避免了不必要的计算。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
int n = nums.length;
Arrays.sort(nums);
List<List<Integer>> ans = new ArrayList<List<Integer>>();
for (int i = 0; i < n; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int p = n - 1;
int target = -nums[i];
for (int q = i + 1; q < n; q++) {
if (q > i + 1 && nums[q] == nums[q - 1]) {
continue;
}
while (q < p && nums[q] + nums[p] > target) {
--p;
}
if (q == p) {
break;
}
if (nums[q] + nums[p] == target) {
List<Integer> list = new ArrayList<Integer>();
list.add(nums[i]);
list.add(nums[q]);
list.add(nums[p]);
ans.add(list);
}
}
}
return ans;
}
}
16.最接近的三数之和(中等)
题目:给你一个长度为 n
的整数数组 nums
和 一个目标值 target
。请你从 nums
中选出三个整数,使它们的和与 target
最接近。
返回这三个数的和。
假定每组输入只存在恰好一个解。
示例 1:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
第一种思路:
这题和上面“三数之和”有相似之处,借鉴上面一题的思想,第一个数在最外层用一个循环变量确定,第二个数在第一个数的后一位,也用一个循环变量确定,第三个数在这两层循环中使用一个while循环寻找,注意:第三个数始终从数组的最后一位开始,所以在第二层循环中重新赋值,定义一个变量sum用于记录三数之和,定义一个变量dis用于记录target和三数之和之间的距离,然后同样可以先将数组进行排序,以及同样的跳过相同指针指向数值相同元素的操作,接着比较target和变量temp(三数之和)之间的距离(差值绝对值)和dis的大小,就用两种情况:
dis > Math.abs(temp-target),当前计算的目标值与三数和距离小于先前计算的距离,故重新给sum和dis赋值(更改sum后,目标值和三数和之间的距离也随之改变)
dis < Math.abs(temp-target),什么都不做
最后无论是两种情况中的哪种情况,right(指向第三个数)都要进行自减操作,因为此时不是等于一个确定值(不能唯一确定第三个数)。
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int sum = 0;
int dis = 100000;
for (int i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1])
continue;
for (int left = i + 1; left < nums.length - 1; left++) {
if (left > i + 1 && nums[left] == nums[left - 1])
continue;
int right = nums.length - 1;
while (left < right) {
int temp = nums[i] + nums[left] + nums[right];
if (dis > Math.abs(temp - target)) {
sum = temp;
dis = Math.abs(temp - target);
}
right--;
}
}
}
return sum;
}
}
当然,这种思路不是很好😜~~
第二种思路:
同样第一个数在最外层用一个循环变量确定,然后计算三数之和,接着核心类似之前做的短板向中间移动,有三种情况:
当三数和刚好等于target的值时,之间返回这时的三数和
当三数和小于target的值,右指针向中间移动,减小三数和
当三数和大于target的值,左指针向中间移动,增大三数和
然后在移动中比较当下的三数和与target的距离和先前三数和与target的距离,不断更改三数和以及三数和与target的距离,这里要注意,初始的sum值要设置大一点或小一点,不然会干扰结果。
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int sum = 100000;
for (int i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1])
continue;
int left = i + 1;
int right = nums.length - 1;
while (left < right) {
int temp = nums[i] + nums[left] + nums[right];
if (temp == target)
return temp;
if (Math.abs(sum - target) > Math.abs(temp - target)) {
sum = temp;
}
if (temp > target) {
while (left < right && nums[--right] == nums[right + 1])
continue;
} else {
while (left < right && nums[++left] == nums[left - 1])
continue;
}
}
}
return sum;
}
}