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

时间:2015-01-20 16:45:37

标签: java algorithm triplet

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

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

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

9 个答案:

答案 0 :(得分:4)

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

话虽如此,以下是您如何使用复杂性O(n^2 log n + k)执行此操作,其中k是答案的大小。 (虽然@saadtaame声称具有相同的复杂性,但他的估计中存在问题,请参阅他的回答下面的评论)。

首先,让我们修复一个元素,比如a[i]。现在让我们创建一个新数组b,其中包含来自a的所有元素,这些元素的索引大于i且值大于a[i]。现在,问题减少为在j中找到kb两个索引,例如j < kb[j] < b[k]

为此,我们可以使用某种排序集,例如Java中的TreeSet。我们将迭代k的所有可能值,维护k中索引小于TreeSet的所有元素。由于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)初始化迭代器并在treeSet处向(3)添加元素最多只需O(log n)次操作。

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

  1. b[k] = 7。此时treeSet为空,因此没有任何反应,并且7被添加到treeSet。

  2. b[k] = 9。这时treeSet有元素7.它小于9,但是总和5 + 7 + 9 > 20,所以我们从树集上的迭代中断。我们将9放到treeSet中,现在包含(7, 9)

  3. b[k] = 8。我们遍历treeSet。对于元素7,满足两个条件(7 < 8 and 5 + 7 + 8 <= 20),因此(5, 7, 8)被添加到答案中。对于元素9,元素大于b[k],因此我们破坏。

  4. 然后k上的循环结束。

    然后我们将i一个元素移到右边。 b的内容将完全相同,并且上述三个步骤几乎相同,只是在第二步中答案将足够小,因此我们将产生(3, 7, 9)和{{1 }}

    然后,当我们移至下一个(3, 7, 8)时,i时,数组a[i] = 7将只包含两个元素b,并且不会生成任何答案。

    我建议使用一些调试输出在Java中编码,然后使用它来更好地理解它。

答案 1 :(得分:3)

我认为可以使用TreeMap或Sorted Map概念在O(n ^ 2logn)时间内解决。 我试图用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);
    }
}

让我知道它是否对您有用。

答案 2 :(得分:1)

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

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

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

现在,在第二个树中找到最大元素A [k],使得A [k] <1。 K-A [1] -A [J]。如果A [k] <= A [i]停止。否则,在第二棵树中打印A [j],A [i],A [k],并设置k = prev(A [k])。环绕直到A [k] <= A [i]。

在所有这些中间,进行额外的比较。令A [j1] = next(A [j])(即A [j1]是排序序列中的下一个元素)。如果对于k,如果它也遵循条件A [j1] + A [i] + A [k] <= t,则记下这样的最大k。并且在下一次迭代中,而不是二进制搜索直接使用此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)。对不起,我没有时间提供详细的证明。

答案 3 :(得分:1)

查找增加的三元组,使得sum小于或等于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)

答案 4 :(得分:0)

哈哈很高兴看到我的博客在这里提到。但是,您关联的帖子会解决d[i]+d[j]+d[k] < 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]&lt; d [j]和i&lt;学家然后在这些对中的每一对上尝试在j之后找到k。

答案 5 :(得分:0)

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

我们要做的是先使用快速排序数字进行排序,这样就可以避免额外的循环,默认情况下我会&lt; j&lt; ķ。

我的逻辑是d [i] + d [j] + d [k]&lt; = t,所以d [k]&lt; = t - d [i] -d [j] 因此,第一个循环将运行1到n,第二个循环从i + 1运行到n,对于第三个循环,我们将进行(t - d [i] -d [j])的二进制搜索并比较d [i]&lt; d [j]&lt; d [k]以避免重复。因此,通过这样做,我们可以获得nlogn(排序)+(n ^ 2 * logn)的复杂性来查找总和。

所以我的电话是:

    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) {
                    ......
                    }
                }
            }
        }
    }

答案 6 :(得分:0)

这里是 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;
}

答案 7 :(得分:-1)

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

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

编辑:OP的第一篇帖子为=,现在他已更改为<=。我更新了我的答案。

答案 8 :(得分:-1)

有条件,i<j<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
            }
        }
    }
}