【问题标题】:Find increasing triplets such that sum is less than or equals to k找到增加的三元组,使得总和小于或等于 k
【发布时间】:2015-03-19 00:00:12
【问题描述】:

这个问题的更简单或更流行的版本是找到具有给定总和的三元组。但这又提出了一个额外的条件。查找未排序数组中的所有三元组,使得

d[i]+d[j]+d[k] <= t;   & d[i]<d[j]<d[k] where i<j<k

THIS 是第一部分问题的解决方案。但是有人可以建议我们如何将其扩展到包括第二个条件。我能想到的唯一方法是在排序时执行自定义数据结构以存储原始元素索引以及数字。然后检查包含链接中提到的算法返回的每个三元组的索引是否按顺序排列。

【问题讨论】:

  • 我几乎可以肯定,这个问题中存在一些组合数学,可能会使mathematics stack exchange 的交叉帖子变得可行。另外,我想我会从对列表进行排序开始。鉴于我们很难在这方面达到小于 O(nlog(n)) 的目标,排序不会增加你的大哦。然后你可以从 i=0, j=i+1, k=j+1 开始,做 3 个嵌套循环。

标签: java algorithm triplet


【解决方案1】:

这里是 c++ 版本

#include <bits/stdc++.h>
using namespace std;

vector<int> helper(vector<int> &A, int T)
{
    vector<int> ans;
    int N = A.size();
    for (int i = 0; i < N; i++)
    {
        vector<int> B;
        for (int j = i + 1; j < N; j++)
        {
            if (A[j] > A[i])
                B.push_back(A[j]);
        }
        set<int> S;
        set<int>::iterator it;
        for (int k = 0; k < B.size(); k++)
        {
            for (auto it = S.begin(); it != S.end(); it++)
            {
                int sum = A[i] + *it + B[k];
                if (*it >= B[k] || sum > T)
                    break;
                cout << A[i] << " - " << *it << " - " << B[k] << endl;
                ans.push_back(sum);
            }
            S.insert(B[k]);
        }
    }
    return ans;
}

vector<int> helper1(vector<int> &A, int T)
{
    vector<int> ans;
    int N = A.size();
    for (int i = 0; i < N - 2; i++)
    {
        for (int j = i + 1; j < N - 1; j++)
        {
            for (int k = j + 1; k < N; k++)
            {
                int sum = A[i] + A[j] + A[k];
                if (sum <= T && A[i] < A[j] && A[j] < A[k])
                {
                    cout << A[i] << " - " << A[j] << " - " << A[k] << endl;
                    ans.push_back(sum);
                }
            }
        }
    }
    return ans;
}

void print(vector<int> &A)
{
    for (int a : A)
        cout << a << " ";
    cout << endl;
}

int main()
{
    vector<int> A({1, 4, 12, 3, 6, 10, 14, 3});
    int T = 11;
    vector<int> ans = helper(A, T); // O(n^2log n)
    print(ans);
    vector<int> ans1 = helper1(A, T); // )(n^3)
    print(ans1);
    return 0;
}

【讨论】:

    【解决方案2】:

    我认为它可以在 O(n^2logn) 时间内解决,使用 TreeMap 或 Sorted Map 概念。 我尝试在 Java 中实现相同的功能,但概念保持不变。

    import java.util.*;
    public class Main
    {
        public static void main(String[] args) {
            int arr[]={1,2,3,3,4,4,9,10,11,342,43};
            int n=arr.length,t=98,cnt=0;
            Arrays.sort(arr);
            for(int k=2;k<n;k++)
            {
                TreeMap<Integer,Integer> ts1=new TreeMap<>();
                for(int j=0;j<k;j++)
                {
                    if(arr[j]==arr[k])
                    break;
                    int i=Math.min(t-arr[k]-arr[j],arr[j]); //try to get the number of elements less than arr[j] and target-arr[k]-arr[j]
                    cnt+=(ts1.lowerKey(i)==null?0:ts1.get(ts1.lowerKey(i)));
                    
                    if(ts1.containsKey(arr[j]))
                    ts1.put(arr[j],ts1.get(arr[j])+1);
                    else
                    {
                        Integer val=ts1.lowerKey(arr[j]);
                        ts1.put(arr[j],1+(val==null?0:ts1.get(val)));
                    }
                }
            }
            System.out.println(cnt);
        }
    }
    

    让我知道它是否适合你。

    【讨论】:

      【解决方案3】:

      找到递增的三元组,使得总和小于或等于 k:

      # include <stdio.h>
      void find3Numbers(int A[], int arr_size, int sum)
      {
          int l, r;
          for (int i = 0; i < arr_size-2; i++){
             for (int j = i+1; j < arr_size-1; j++){         
                 for (int k = j+1; k < arr_size; k++){
                     if (A[i] + A[j] + A[k] <= sum)
                       printf("Triplet is %d, %d, %d\n", A[i], A[j], A[k]);
                  }
              }
           }
      }
      int main()
      {
          int A[] = {1, 2, 3, 4, 6};
          int sum = 8;
          int arr_size = sizeof(A)/sizeof(A[0]);
          find3Numbers(A, arr_size, sum);
          return 0;
      }
      

      输出:

      Execution :
      arr_size = 5
      Step:1   i=0 and i<3 (arr_size-2)
                                      j=1 and j<4 (arr_size-1)
                                                      k=2 and k<5 (arr_size)
                                                                      A[0]+A[1]+A[2]<=sum --> 1+2+3 <=8 --> 6<=8 ( true )
                                                      k=3 and k<5
                                                                      A[0]+A[1]+A[3]<=sum --> 1+2+4 <=8 --> 7<=8 ( true )
                                                      k=4 and k<5
                                                                      A[0]+A[1]+A[4]<=sum --> 1+2+6 <=8 --> 9<=8 ( false )
                                      j=2 and j<4
                                                      k=3 and k<5
                                                                      A[0]+A[2]+A[3]<=sum --> 1+3+4 <=8 --> 8<=8 ( true )
                                                      k=4 and k<5
                                                                      A[0]+A[2]+A[4]<=sum --> 1+3+6 <=8 --> 10<=8 ( false )
                                      j=3 and j<4
                                                      k=4 and k<5
                                                                      A[0]+A[3]+A[4]<=sum --> 1+4+6 <=8 --> 11<=8 ( false )
                                      j=4 and j<4 (false)
      Step:2  i=1 and i<3
                                      j=2 and j<4
                                                      k=3 and k<5
                                                                      A[1]+A[2]+A[3]<=sum --> 2+3+4 <=8 --> 9<=8 ( false )
                                                      k=4 and k<5
                                                                      A[1]+A[2]+A[4]<=sum --> 2+3+6 <=8 --> 11<=8 ( false )
                                      j=3 and j<4
                                                      k=4 and k<5
                                                                      A[1]+A[3]+A[4]<=sum --> 2+4+6 <=8 --> 12<=8 ( false )
                                      j=4 and j<4 (false)
      Step:3 i=2 and i<3
                                      j=3 and j<4
                                                      k=4 and k<5
                                                                      A[2]+A[3]+A[4]<=sum --> 3+4+6 <=8 --> 13<=8 ( false )
                                      j=4 and j<4 (false)
      Step:4 i=3 and i<3 (false)
      

      【讨论】:

      • 这被投票赞成删除低质量,我修复了它。您提到您不知道如何格式化您的代码,这可以帮助您:stackoverflow.com/editing-help 另外请记住,仅链接到您自己的博客的答案有点可疑,因为它闻起来有自我宣传的味道。在您的回答中,始终放置一个标题,说明您打算做什么、它是如何工作的以及结果是什么,即使它在问题中明确说明,因为我们想知道您的观点是什么。此外,如果您可以减少缩进以使您的输出适合窗口,那就太好了。
      【解决方案4】:

      我认为在这种情况下可以实现n^2*logn。

      我们要做的是首先使用快速排序数字进行排序,以避免额外的循环,默认情况下 i

      我的逻辑是 d[i] + d[j] + d[k]

      所以我的电话是:

          Arrays.sort(d);
          for (int i = 0; i < d.length; i++) {
              for (int j = i + 1; j < d.length; j++) {
                  int firstNumber = d[i];
                  int secondNumber = d[j];
                  int temp = t - firstNumber - secondNumber;
                  if ((firstNumber < secondNumber) && (secondNumber < temp))  {
                      int index = Arrays.binarySearch(d, temp);
                          if (index >= 0) {
                          ......
                          }
                      }
                  }
              }
          }
      

      【讨论】:

        【解决方案5】:

        我认为你可以做得比 O(n^2 log n + k) 稍微好一点。

        假设你在位置 i。 i(1...i)之前的所有元素都存储在一个BST中,i(1+1...n)之后的所有元素都存储在第二个BST中。

        在第一棵树中,找到最小的元素 A[j] 使得 A[j]

        现在,在第二棵树中找到最大元素 A[k],使得 A[k]

        在这一切的中间,做一个额外的比较。令 A[j1] = next(A[j]) (即 A[j1] 是排序序列中的下一个元素)。如果对于 k 如果它也遵循条件 A[j1]+A[i]+A[k]

        最初从 i=2 开始,firstTree={A[1]},secondTree = {A[3]..A[n]}。并且每当您增加 A[i] 时,将 A[i] 添加到 firstTree 并从第二棵树中删除 A[i+1]。

        总体时间复杂度:O(n^2+n*log(n)) + O(p) = O(n^2+p) 其中 p 是总结果数。

        算法:

        Initialization: firstTree={A[1]}, secondTree = {A[3]..A[n]}
        
        For i = 2:(n-1) do:
            j = getFirst(firstTree)
            firstIter = true;
            while(true)
                if A[j]>=A[i] or A[j]+A[i]>t break
                if firstIter:
                    k = binSearch(secondTree, t - A[i] -A[j])
                    firstIter=false
                else
                    if bestk<0:
                        break;
                    k = bestk
                bestk = -1
                jnext = nextElement(firstTree, A[j]);
                while (A[k]>A[i]):
                    print (A[j], A[i], A[k])
                    if bestk<0 and A[i]+A[jnext]+A[k] < t:
                        bestk = k;
                    k = prevElement(secondTree, A[k])
            Add(firstTree, A[i])
            Remove(secondTree, A[i+1])
        

        请注意,每个 i 只调用一次 binSearch,并且对于其中一个 i,nextElement 恰好遍历树一次(复杂度 O(n))。内部 while 循环只被调用一次。所以总体复杂度是 O(n^2 + nlogn + p),其中 p 是输出的数量。

        编辑:因为如果我们发现一个没有结果的 j(即 j 没有解决方案),我们就会停下来。我认为这个成本包含在 O(p) 本身中。所以最终的复杂度是 O(nlogn + p)。抱歉,我没时间提供详细的证明。

        【讨论】:

        • 而且您甚至不需要 BST,您只需添加线性次数,因此排序数组而不是树就可以了,并且会简化解决方案。
        • @Ishamael 是的。但我有一种感觉,它实际上不是 O(n^2),而是成本将成为 O(p) 的一部分(原因是一旦你发现一个没有结果的 j 你就停下来)。但是太忙了,无法证明这一点!
        • 很好的一点,因为print 的执行次数不会超过p 次,我们只需要证明,while (true) 的不调用 print 的迭代次数是恒定的。事实上,如果在while (true) 的其中一次迭代中您没有调用打印,那么您也没有分配bestk,这意味着在while(true) 的下一次迭代中您将中断。因此,while (true) 将在所有 is 上进行 O(p + i) 迭代,从而使您的方法变得复杂 O(n log n + p),这太棒了。
        【解决方案6】:

        首先,值得指出的是,最坏情况下的复杂度不能比O(n^3)好,因为在最坏情况下有O(n^3)三元组,显然每个三元组至少需要恒定时间,以存储/打印它。还有一个非常简单明了的O(n^3)算法。

        话虽如此,这就是您如何以复杂的方式做到这一点O(n^2 log n + k),其中k 是答案的大小。 (虽然@saadtaame 声称具有相同的复杂性,但他的估计存在问题,请参阅他的答案下方的 cmets。

        首先,让我们修复一个元素,比如a[i]。现在让我们创建一个新数组b,由a 中的所有元素组成,它们的索引都大于i,值大于a[i]。现在问题简化为在b 中找到两个索引jk,即j &lt; kb[j] &lt; b[k]

        为此,我们可以使用某种排序集,例如 Java 中的 TreeSet。我们将遍历k 的所有可能值,在TreeSet 中维护所有索引小于k 的元素。由于TreeSet 只包含索引小于k(因为我们构建它的方式)和大于i(因为b 只包含这样的元素)的元素,并且被排序,那么每个元素q 中的TreeSet 的值小于b[k] 形成一个答案三元组(a[i], q, b[k])。这是一个伪代码:

        for i from 0 to size(a):
            b = empty array
            for j from i + 1 to size(a):
                if a[j] > a[i]:
                    add a[j] to b
            treeSet = new TreeSet
            for k from 0 to size(b):
                for each element 'e' in the treeSet in sorted order: // (1)
                    if e >= b[k] or a[i] + e + b[k] > t:
                        break
                    add (a[i], e, b[k]) to the answer // (2)
                add b[k] to the treeSet // (3)
        

        这里如果我们返回的元素个数小于O(n^2 log n),那么算法的复杂度将是O(n^2 log n)。原因是(2) 行被精确地执行了k 次,因此可以忽略(并且迭代一个treeSet 在元素数量上摊销了线性时间),而内部循环的其余部分:将迭代器初始化为(1) 和在(3) 处向treeSet 添加一个元素最多都是O(log n) 操作。

        编辑:这是一个小例子。假设数组是a = [5, 3, 7, 9, 8, 1]t = 20。然后i首先指向5,我们把所有从5向右和更大的元素放到b,所以b = [7, 9, 8]。然后k会做3次迭代:

        1. b[k] = 7。此时 treeSet 为空,所以什么也没有发生,7 被添加到 treeSet 中。

        2. b[k] = 9。此时 treeSet 有元素 7。它小于 9,但总和为5 + 7 + 9 &gt; 20,因此我们中断了对 treeSet 的迭代。我们将9 放到treeSet 中,现在集合中包含(7, 9)

        3. b[k] = 8。我们遍历树集。对于元素 7,两个条件都满足 (7 &lt; 8 and 5 + 7 + 8 &lt;= 20),因此将 (5, 7, 8) 添加到答案中。对于元素 9,该元素大于 b[k],因此我们中断。

        那么k的循环就结束了。

        然后我们将i 向右移动一个元素。 b 的内容会完全一样,上面的三个步骤也差不多,只是第二步的时候答案足够小,所以我们会产生(3, 7, 9)(3, 7, 8)

        那么当我们移动到下一个i 时,当a[i] = 7 时,数组b 将只包含两个元素[9, 8],并且不会产生任何答案。

        我建议用 Java 编写带有一些调试输出的代码,然后尝试一下以更好地理解它。

        【讨论】:

        • 非常感谢伊沙梅尔。是否可以添加一个示例。我也认为缺少代码..您在树集中的哪里添加元素?
        • 我在最后一行添加到 treeSet。在第一次迭代中,treeSet 是空的,所以循环自然不会做任何事情,第一个元素将被添加,真正的工作将从 k 的第二次迭代开始。
        • 我加了一个小例子。很难找到一个适用于所有极端情况的示例,我建议对其进行编码,在代码中添加一些打印以查看它的作用,然后在一些示例上运行。
        【解决方案7】:

        有条件,i&lt;j&lt;k 一半的立方体不符合条件,因此您可以消除不需要的部分

        for (int k = 0; k < N, k++) 
        {
            for (int j = 0; j < k, k++) 
            {       
                        if (d[j] < d[k]) continue;
                for (int i = 0; i < j, i++) 
                {
                    if (d[k] < d[j]) continue;
                    if (d[i]+d[j]+d[k] <= t) 
                    {
                         //valid result
                    }
                }
            }
        }
        

        【讨论】:

          【解决方案8】:

          哈哈,很高兴在这里看到我的博客。但是您链接的帖子解决了d[i]+d[j]+d[k] &lt; t

          我觉得你应该清楚的问一下你的数组是不是排序的,那么你的问题就是How to find pairs that equal to a specific sum in an array的轻微扩展

          因此,对于您的情况(假设数组已排序),您只需确保 i、j、k 始终按升序排列。

          因此,考虑到 N 个元素和一个目标 T,一个经典的昼夜方法(即 j、k 相互靠近),您可以尝试:

          for (int i = 0; i < N, i++) {
              int j = i;
              int k = N-1;
              while (j < k) {
                  int currSum = i+j+k;
                  if (currSum < T) { // increase sum
                      j++;
                  }
                  else if (currSum > T) { // decrease sum
                      k--;
                  }
                  else {
                      System.out.println("Found triple: " + i + ", " + j + ", " + k);
                      j++;
                  }
              }
          }
          

          //i,j,k保证是递增顺序的。

          如果数组没有排序,你可以做一个优化的蛮力。所以找到所有 i, j 使得 d[i]

          【讨论】:

          • 感谢您的回复。是的,我应该提到数组没有排序。
          • 我也写错了问题。我想问 d[i]+d[j]+d[k]
          • 等待回音 :)
          【解决方案9】:

          如果修复 i 和 j,则必须在数组中找到一个小于或等于 t - d[i] - d[j] 的数字。存储数组的第二个版本,其中每个元素也存储它的索引。按升序对该数组进行排序。

          现在,对于 i, ji &lt; j 的所有对(这只是两个嵌套的 for 循环),您对 t - d[i] - d[j] 进行二进制搜索;如果存在这样的数字,则从左到右遍历所有数字并检查它们的索引是否大于 j,如果是,则添加到输出。复杂度为O(n*n*lg n + k),其中 k 是满足条件的输出数。

          编辑:OP 的第一个帖子是=,现在他改为&lt;=。我更新了我的答案。

          【讨论】:

          • 您的复杂性是错误的。从左边到找到的数字具有线性复杂度,并且您在两个嵌套循环中进行,因此复杂度为 O(n^3)。
          • @Ishamael 是正确的..再读一遍,排序是使它成为可能的原因 + 我的界限是输出敏感的(如果你仔细观察,那里有一个 k 并且 k 可能是O(n^3) :)
          • 这是不正确的,因为当您从排序数组的左侧扫描到通过二进制搜索找到的元素时,您不能保证您扫描的元素实际上对答案有贡献。他们可能违反a[i] &lt; a[j] &lt; a[k] 的条件。数组按值排序,而不是按索引排序。所以你在每次二分搜索之后你可能会做线性工作,而k 非常小,导致O(n^3) 复杂性,而k 小得可以忽略不计。
          • 作为一个例子,考虑具有n / 3两个后跟n / 3零后跟n / 3三个的数组,并且t非常大。这个数组没有答案,所以k 为零,但是对于每个i, j 这样a[i] = 2, a[j] = 3(还有O(n^2) 这样的对),您的二分搜索将在所有零之后返回排序数组中的一个位置,你将不得不扫描所有O(n) 零,并拒绝所有它们,导致O(n^3) 复杂性,而k = 0
          猜你喜欢
          • 2013-07-16
          • 1970-01-01
          • 1970-01-01
          • 2021-06-04
          • 1970-01-01
          • 2016-12-20
          • 2020-01-10
          • 2019-09-11
          • 2022-10-12
          相关资源
          最近更新 更多