【问题标题】:Find all unique triplets that sum to zero找出总和为零的所有唯一三元组
【发布时间】:2018-10-06 09:16:58
【问题描述】:

在一次采访中得到了这个。该问题与非常著名的问题Find a triplet that sum to a given value 非常相似,略有不同。在这里,我们要打印 ALL 三元组,而不仅仅是一个。

数组可以包含重复项。

例如考虑以下数组: [1, -1, 2, 0, -2, 4, -2, -2, 4]

输出应该是:

[1, -1, 0]
[4, -2, -2]
[2, -2, 0]

三元组的顺序或三元组中数字的顺序并不重要。

使用排序或使用集合有 n^2 个解决方案(如上面链接中的解决方案)。但是如何确保我们只打印独特的三元组呢?我能想到的一个解决方案是使用一组来跟踪我们到目前为止看到的三元组。但不确定这是否是最好的方法,或者是否有其他解决方案使用排序等来生成独特的三元组。

【问题讨论】:

    标签: algorithm


    【解决方案1】:

    没有太大的不同。只有当我们有重复项时,我们才需要在每个级别上跳过它们,所以std::set 不一定。

    #include <vector>
    #include <algorithm>
    #include <iostream>
    #include <array>
    int main()
    {
        const int kDesired = 0;
        std::vector<int> a = { 1, -1, 2, 0, -2, 4, -2, -2, 4 };
        std::sort(a.begin(), a.end());
        std::vector<std::array<int, 3>> triples;
        for (int i = 0; i < (int)a.size(); ++i) {
            const int others = kDesired - a[i];
            for (int j = i + 1; j < (int)a.size(); ++j) {
                for (int k = (int)a.size() - 1; k > j; --k) {
                    if (a[j] + a[k] == others) {
                        triples.push_back({ { a[i], a[j], a[k] } });
                    }
                    else if (a[j] + a[k] < others) {
                        break;
                    }
                    // we don't want a[k] to be the same next time
                    while (j + 1 < k && k < (int)a.size() && a[k] == a[k - 1]) --k;
                }
                // we don't want a[j] to be the same next time
                while (i + 1 <= j && j < (int)a.size() - 1 && a[j] == a[j + 1]) ++j;
            }
            // we don't want a[i] to be the same next time
            while (0 <= i && i < (int)a.size() - 1 && a[i] == a[i + 1]) ++i;             }
        for (const auto& t : triples) {
            std::cout << t[0] << " " << t[1] << " " << t[2] << std::endl;
        }
        return 0;
    }
    

    -2 -2 4
    -2 0 2
    -1 0 1

    online

    【讨论】:

      【解决方案2】:

      Python 解决方案: 时间复杂度:O(n^2) 空间复杂度:O(1)

      class Solution:
      def findSum(self , nums , rest_sum , start_from , soln ):
          i = start_from +1  
          j = len(nums)-1
          while(i < j ):
              if(nums[i] + nums[j] < rest_sum):
                  i += 1
              elif(nums[i] + nums[j] > rest_sum):
                  j -= 1
              else:    
                  soln.append( [ nums[start_from] , nums[i] , nums[j] ])
                  i += 1
                  j -= 1
                  while(i < j and nums[i] == nums[i-1]):#Loop to avoid duplicate
                      i+=1
                      continue
                  while(j > i and nums[j] == nums[j+1] ):#Loop to avoid duplicate
                      j-=1
                      continue                             
          return 
      
      
      def threeSum(self, nums: List[int]) -> List[List[int]]:
          if(len(nums) < 3 ):
              return []
          soln = []
          nums.sort()
      
          for i in range(0 , len(nums)):
              if(i > 0 and nums[i-1] == nums[i]):#Loop to avoid duplicate
                  continue
              self.findSum(nums , (0 - nums[i]) ,i , soln ) #Use Two Sum Algo to find solution
      
          return soln
      

      【讨论】:

      • 解释得很好,边界情况如此直观以避免重复。
      【解决方案3】:
      • 对数组进行排序后,您可以跳过之前访问过的相同整数。
      • 时间复杂度为 O(N2log (N))。以下是相同的示例(我更喜欢 Java)。

      代码:

      import java.util.*;
      public class Solution{
          public static void main(String[] args) {
              List<List<Integer>> res = new ArrayList<List<Integer>>();
              int[] arr = {1,-1,2,0,-2,4,-2,-2, 4,-2,-2,-2,4,4,4};
              int target = 0;
              Arrays.sort(arr);
      
              int low = 0,mid = 0,high = 0,seek = 0;
              for(int i=0;i<arr.length;++i){
                  if(i > 0 && arr[i] == arr[i-1]) continue;// skip it to avoid getting same triplets
                  for(int j=i+1;j<arr.length;++j){
                      if(j > i+1 && arr[j] == arr[j-1]) continue; // skip it to avoid getting same triplets
                      seek = target - arr[i] - arr[j];
                      if(seek < arr[j]) break; // we break because seek cannot be found ahead if arr[j] is greater than it after sorting. 
                      low = j+1;
                      high = arr.length-1;        
                      while(low <= high){
                          mid = low + (high - low) / 2;
                          if(arr[mid] == seek){
                              // add this triplet to results.
                              List<Integer> triplet = new ArrayList<>();
                              triplet.add(arr[i]);
                              triplet.add(arr[j]);
                              triplet.add(seek);
                              res.add(triplet);
                              break;
                          }else if(arr[mid] > seek){
                              high = mid - 1;   
                          }else{
                              low = mid + 1;   
                          }
                      }
                  }
              }
      
              System.out.println(res.toString());
          }
      }
      

      输出:

      [[-2, -2, 4], [-2, 0, 2], [-1, 0, 1]]
      

      【讨论】:

        【解决方案4】:

        多亏了 Yola 的回答,我才真正理解了如何避免重复,所以这里是 O(n^2) 的解决方案。

        主要思想是主循环遍历每个唯一数字,然后尝试找到其他两个数字,它们的总和为 0。

        主要技巧是对数组进行排序,然后 i、j、k 中的每一个在他们的轮次中不访问任何重复的数字,并且保证不会产生任何重复的三元组。

        import java.util.Arrays;
        
        public class Find3TripletSum0 {
        
            public static void find(int a[]) {
                Arrays.sort(a);
                for (int i = 0; i < a.length; i++) {
                    if (i > 0 && a[i] == a[i - 1]) // pass duplicates for i
                        continue;  
                    int j = i + 1; 
                    int k = a.length - 1; 
                    int target = -a[i];
                    while (j < k) {
                        if (j > i + 1 && a[j] == a[j - 1]) { // pass duplicates for j
                            j++;
                            continue; 
                        }
                        if (k < a.length - 1 && a[k] == a[k+1]) { // pass duplicates for k
                            k--;
                            continue; 
                        }
                        if (a[i] + a[j] + a[k] == 0)
                            System.out.printf("[%d, %d, %d]\n", a[i], a[j], a[k]);
                        if (a[j] + a[k] < target)
                            j++; 
                        else
                            k--; 
                    }
                }
            }
        
            public static void main(String[] args) {
                int a[] = {1, -1, 2, 0, -2, 4, -2, -2, 4};
                find(a);
            }
        }
        

        输出:

        [-2, -2, 4]
        [-2, 0, 2]
        [-1, 0, 1]
        

        【讨论】:

          【解决方案5】:

          看这个复杂度为O(n^2)的解,不知道它的复杂度能不能降到线性。

          public static List<List<Integer>> findTriplets(int nums[]) {
                  boolean found = false;
                  List<Integer> triples = null;
                  HashSet<Integer> set = null;
                  HashSet<List<Integer>> tripleSet = new HashSet<List<Integer>>();
                  for (int i = 0; i < nums.length - 1; i++) {         
                      set = new HashSet<Integer>();
                      for (int j = i + 1; j < nums.length; j++) {
                          found = false;
                          int x = -(nums[i] + nums[j]);
                          if (set.contains(x)) {
                              Integer [] temp = {x,nums[i],nums[j]};
                              Arrays.sort(temp);
                              triples = new ArrayList<Integer>();
                              triples.add(temp[0]);
                              triples.add(temp[1]);
                              triples.add(temp[2]);
                              found = true;
                          } else {
                              set.add(nums[j]);
                          }
                          
                          if(found==true){
                              tripleSet.add(triples);
          
                          }
                          
                      }
                  }
                  return new ArrayList<List<Integer>>(tripleSet);
              }
          

          【讨论】:

            【解决方案6】:

            如果您正在寻找 javascript 中的解决方案,这里有一个用简单的英语解释逻辑的解决方案:

            const threeSum = (nums, target) => {
              const hash = {};
              const ans = [];
              for (let i = 0; i < nums.length; i++) {
                for (key in hash) {
                  if(hash[key][target - (nums[i] + +key)] === undefined) {
                    hash[key][nums[i]] = null;
                  } else {
                    hash[key][target - (nums[i] + +key)] = nums[i];
                    ans.push([+key, target - (nums[i] + +key), nums[i]]);
                  }
                }
                if (hash[nums[i]] === undefined) {
                  hash[nums[i]] = {}
                }
              }
              return ans;
            }
            

            示例

            console.log(threeSum([-1, 0, 1, 2, -1, -4], 0));
            

            输出

            [ [ -1, 0, 1 ], [ 0, 1, -1 ], [ -1, 2, -1 ] ]
            

            说明

            {
                -1: {0:1, 1:null, 2:-1, -1:null, -4:null},
                0: {1:-1, 2: null, -1:null, -4:null},
                1: {2: null, -1:null, -4: null},
                2: {-1:null, -4:null},
                -4: {}
            }
            
            1. 遍历数组>如果不在对象中,则添加到对象中
            2. 遍历对象> 如果不在对象的对象中,则将其添加为null |其他日志

            【讨论】:

              【解决方案7】:
              import java.util.*;
              class zero
              {
              public static void main(String abc[])
              {
              int arr[]=new int[6];
              int i,j,x;
              
              
              Scanner c=new Scanner(System.in);
              
              System.out.println("Enter an array to be sorted ");
                  for(i=0;i<6;i++)
                   {
                    arr[i]=c.nextInt();
                    }
              
              Arrays.sort(arr);
              
              for(i=0;i<6;i++)
                   {
                    System.out.println(arr[i]);
                  }
              
              for(i=0;i<4;i++)
              {
              x=arr[i]*-1;
              j=5;
               while(i<j)
               { 
                if(arr[i+1]+arr[j]>x)
                 {
                  j--;
                 }else if(arr[i+1]+arr[j]<x){
                  i++;
                 }else{ 
                 System.out.println("Found ");
                 break;
                  }
               }
              }
              
              }
              }
              

              【讨论】:

                【解决方案8】:

                只有当当前号码与您已经看到的号码相同时,才会出现重复。可以使用 HashSets。

                但是,由于您正在对数组进行排序,因此您只需要跟踪前一个数字,因为排序可确保所有重复项都在同一个位置。

                这是 C# 中的解决方案。

                public class Solution 
                {
                  public IList<IList<int>> ThreeSum(int[] nums)
                  {
                    var result = new List<IList<int>>();
                    
                    if(nums.Length<3)
                        return result;
                    
                     Array.Sort(nums);
                    int curr=nums[0]-1;
                   
                    
                    for(int i=0; i<nums.Length;i++)
                    {
                        if(nums[i]!=curr)
                        {
                            var tsum= Get2Sum(-nums[i],nums, i+1);
                            
                            if(tsum.Count>0)
                            {
                                for(int j=0; j<tsum.Count;j++)
                                {
                                    tsum[j].Add(nums[i]);
                                }
                                
                                result.AddRange(tsum);
                            }
                            
                            
                            curr=nums[i];
                        }
                    }
                    return result;
                    
                }
                
                public IList<IList<int>> Get2Sum(int target, int[] nums, int start)
                {
                    
                    var result = new List<IList<int>>();
                    int end = nums.Length-1;
                    if(start>=end)
                    {
                        return result;
                    }
                    //current value for starting pointer
                    int scurr=nums[start]-1;
                    //current value for ending pointer
                    int ecurr= nums[end]+1;
                    
                    while(start< end)
                    {
                        if(nums[start]==scurr)
                        {
                            start++;
                            continue;
                        }
                        if(nums[end] == ecurr)
                        {
                            end--;
                            continue;
                        }
                        
                        int sum = nums[start]+nums[end];
                        if(sum > target)
                        {
                            ecurr= nums[end];
                            end--;
                        }
                        else if(sum < target)
                        {
                            scurr=nums[start];
                            start++;
                        }
                        else
                        {
                            scurr=nums[start];
                            ecurr=nums[end];
                            result.Add(new List<int>(){nums[start], nums[end]});
                            start++;
                            end--;
                        }
                        
                        
                    }
                    
                    return result;
                    
                  }
                }
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2020-08-24
                  • 2015-12-17
                  • 2013-07-16
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-07-25
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多