算法入门-数组3

第一部分:数组

15.三数之和(中等)

题目:给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != 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;
    }
}

  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值