• LeetCode (13): 3Sum Closest


     https://leetcode.com/problems/3sum-closest/

    【描述】

    Given an array S of n integers, find three integers in S such that the sum is closest to a given number, target. Return the sum of the three integers. You may assume that each input would have exactly one solution.

        For example, given array S = {-1 2 1 -4}, and target = 1.
    
        The sum that is closest to the target is 2. (-1 + 2 + 1 = 2).

    【中文描述】

    给定一个数组n个数字,并且给一个target数,要求从这个数组中找出3个数字,其和最接近这个target。比如上面例子显示的那样。

    同时,leetCode很仁慈的说,假定每个数组中只会有一个唯一解。

    ————————————————————————————————————————————————————————————

    【初始思路】

    这道题其实和3SUM普通那道题差不多(3SUM下面讨论一下)。  无非就是找最接近target的3元组。那我们完全可以套用3SUM的代码,稍加改动即可。

    怎么改?

    首先,3SUM的代码其实是把所有3元组全部算出来了。那我们在计算所有3SUM的同时,实时地把最接近的3元组记录下来不就好了。然后在计算结束后,我们返回最接近的这个值不就完了?

    此外,low和high的跳跃和3SUM的原则是一样的,如果比target还大,high--,如果比target小,low++.  关于3SUM算法下面会讨论,这种跳跃方法,low和high肯定能计算到所有的数字,不会有漏掉的可能。

    【Show me the Code!!!】

     1 public static int threeSumClosest(int[] nums, int target) {
     2         int len = nums.length;
     3         Arrays.sort(nums);
     4 
     5         //算出最大可能和 + 最小可能和, 避免最差情况
     6         int max = nums[len - 1] + nums[len - 2] + nums[len - 3];
     7         int min = nums[0] + nums[1] + nums[2];
     8         if(target >= max) return max;
     9         if(target <= min) return min;
    10 
    11         int diff = Integer.MAX_VALUE;
    12         int result = 0;
    13         for (int i = 0; i < len; i++) {
    14             int low = i + 1;
    15             int high = len - 1;
    16             while (low < high) {
    17                 int sum = nums[i] + nums[low] + nums[high];
    18                 if(Math.abs(target-sum) < diff) { //只要比diff小,就更新diff,同时记录三元组和
    19                     diff = Math.abs(target-sum);
    20                     result = sum;
    21                 } else {
    22                     if(sum > target) high--;
    23                     else low++;
    24                 }
    25             }
    26         }
    27         return result;
    28     }
    3SumClosest

    关于3Sum题的反思和分析

    https://leetcode.com/problems/3sum/

    题目描述:

    Given an array S of n integers, are there elements abc in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.

    • Elements in a triplet (a,b,c) must be in non-descending order. (ie, a ≤ b ≤ c)
    • The solution set must not contain duplicate triplets.

             For example, given array S = {-1 0 1 2 -1 -4},

        A solution set is:
        (-1, 0, 1)
        (-1, -1, 2)

    中文描述:

    给一个数组,要求找出其中的和为0的三个数字的组合。   要求找出所有,并且,所有3元组都必须升序排列,并且不能有重复。看例子就能看懂。

    返回类型根据题目要求是: List<List<Integer>> , 一个数字列表的列表。

    【思路】

    先考虑会不会有特殊情况,自己试了几个例子,发现,如果数组为null或者数组元素个数小于3个的时候,统一返回了空list,这些情况可以单独写代码应对。

    接下来就考虑一般情况。

    对于数组题,结果不要求返回位置相关信息的。最好都先考虑排序一下,然后再计算,会更简单直观,这个题也不例外。先排序一下:Arrays.sort(nums);

    排序后呢?

    由于根据题意,要求三元组相加和等于0. 那么,可以肯定的是,需要遍历数组,对于每一个数字nums[i],在余下的数字中找出另外两个数字使其满足加起来=0的条件。

    如何在剩余的数组中找到我们想要的两个数字是本题的技巧所在。

    最简单的办法是i, j指针2层循环。这是蛮力算法,仔细想想你会发现,其实很多计算都是不必要的。

    首先,数组是已经从小到大排序好的,而这两个数之和是个固定值0-nums[i], 我们可不可以在一次遍历里用两个指针,其和等于0-nums[i]是一种情况, 其和不等于0-nums[i]是另外一种情况,之后移动两个指针进行下一次计算不就可以了么?

    两个指针遍历一维数组,首先想到的办法就是两边各放一个,然后往中间靠拢。我们来分析一下可不可行。

    假设,两个指针:low和high, low指向i+1(因为nums[i]已经确定了,low应该从i的下一位开始遍历), high指向数组最后一个元素。 两者相加,无非3种可能性:(1)大于target(2)小于target(3)等于target。挨个分析一下:

    (1)大于target. 由于数组已经排序好了,在nums[low] + nums[high] > target后,由于我们要对两个指针做微调,让它尽可能达到target值。假设我们调low,low+1。那么由于nums[low+1] > nums[low],所以low移动后的nums[low] + nums[high] > target 就是必然的了!事实上,这个值比上面那个值还大!所以这个情况下,应该移动high,让high-1。 那么nums[low] + nums[high]就比上面的值稍小一点,靠近了一点target,然后才有可能和target相等;

    (2)小于target. 同理,这个时候应该移动low+1,而不是high-1. 

    (3)如果nums[low] + nums[high] = target。 那么low和high需要各自向里面移动一位。low++,high--。

    这样,就能够保证遍历到所有的可能性。

    【重复元素!】

    题目没有说没有重复元素,我试了几个例子,确实是,如果有重复元素,官方的结果里是去重的。说明可能会有重复元素的用例。

    那怎么办呢?上面的办法,如果不加任何实时去重机制,那结果里肯定会有一大堆重复结果。如果我们在结果里去重,就慢得多了。因为首先重复计算了很多结果,最终又得去掉他们,做了无用功。

    那我们就考虑如何在上面的机制里实时去重。

    还是回到数组已经排序的特性上来,考虑下面的序列:

               -3,  -1, -1, -1, -1, 0,   1,   1,   1,   4,   4,   5

                i    low                                                 high

    当i指向-3的时候,起初low在-1的位置,而high在5的位置,-1+5 > 3,high回退到4。 -1+4 = 3。这个时候成功了,好我们记录下这组结果。记录结束后呢?如何处理?直接low++,high--?

    那么low+1又到了-1,而high-1后到了4(如下面序列), -1+4 =3, 这组结果就肯定被记录下来了。这就出现了重复结果。

        -3,  -1, -1, -1, -1, 0,   1,   1,   1,   4,   4,   5

                i          low                                high

    考虑排序数组的特性,由于当low在-1的时候已经计算了一个结果了(-3,-1,4),那么下一个low遇到-1的时候,是不是可以直接跳过?显然,跳过后对结果没有任何影响,因为当前值只要不变,我们想找的另外一个值肯定不会变!而对于high,也应该同步跳过4,因为low已经跳过了-1到达了0,4就绝对不可能是合理的结果了(0+4>3)。所以high也需要跳过4。

    可以了么?考虑下面的串:

        -3, -3  -1, -1, -1, -1, 0,   1,   1,   1,   4,   4,   5

                i        low                                                  high

    当i指向第一个-3的时候,我们势必能计算出一些结果来。等low和high在中间相遇的时候,此轮遍历结束,i++。这个时候i又指向了-3,再计算一遍的话,显然会有重复结果。怎么办?

    我们在上面推移low和high的同时,其实可以同步推移i,因为 i 的值对循环内部的算法不产生任何影响,我们直接把指向第一个-3的i指向第二个-3。 相当于忽略掉重复的-3。 这样,本轮循环结束后,i++之后,肯定就会到达下一个新值,而不再重复-3. 这个是可行的

    最后,我们看看代码

     1 public List<List<Integer>> threeSum(int[] nums) {
     2         List<List<Integer>> result = new ArrayList<List<Integer>>();
     3         if(nums == null || nums.length < 3) return result;
     4         Arrays.sort(nums);
     5         for(int i = 0; i < nums.length; i++){
     6             int low = i+1;
     7             int high = nums.length -1;
     8             while(low < high){
     9                 if(nums[i] + nums[low] + nums[high] == 0){
    10                     result.add(Arrays.asList(nums[i], nums[low], nums[high]));
    11                     while(i + 1 < nums.length && nums[i + 1] == nums[i]) i++;//去重考虑.
    12                     while(low + 1 < nums.length && nums[low] == nums[low + 1]) low++;//因为i移动的话,low肯定要移动.最终low肯定停留在i后一位,所以没问题
    13                     while(high - 1 >= 0 && nums[high] == nums[high - 1]) high--;//去重考虑
    14                     /**
    15                      * 去重结束后,low和high往中间汇合
    16                      * 并且,在当前low+high = 0 - nums[i]的情况下,low和high是唯一的组合.所以可以同时推移
    17                      */
    18                     low++;
    19                     high--;
    20                 } else if(nums[i] + nums[low] + nums[high] > 0) {
    21                     /**
    22                      * 不等情况(1)
    23                      */
    24                     high--;
    25                 } else low++;//不等情况(2)
    26             }
    27         }
    28         return result;
    29     }
    3Sum
  • 相关阅读:
    汉语6级试卷(转)
    看《第一滴血4》
    又爱又恨奥沙利文
    最爱“剁椒鱼头”
    中国移动用户请注意长途电话包有陷阱
    推荐一个免费原版KTV / MTV下载网站
    传说中的人品问题
    信息更改致乒乓球爱好者
    贴几张我家乐乐的照片
    Web Parts: From SharePoint to ASP.NET 2.0
  • 原文地址:https://www.cnblogs.com/lupx/p/leetcode-3sum.html
Copyright © 2020-2023  润新知