繁体   English   中英

给定一个已排序的数组和一个参数 k,求线性时间内大于或等于 k ​​的两个数之和的计数

[英]Given a sorted array and a parameter k, find the count of sum of two numbers greater than or equal to k in linear time

我试图在总和等于 k ​​的数组中找到所有对。 我当前的解决方案需要 O(n*log(n)) 时间(下面的代码片段)。任何人都可以帮助我找到更好的解决方案,O(n) 或 O(lgn) 可能是(如果存在)

  map<int,int> mymap;
  map<int,int>::iterator it;

  cin>>n>>k;

  for( int i = 0 ; i < n ; i++ ){

    cin>>a;
    
    if( mymap.find(a) != mymap.end() )
        mymap[a]++;
    else    
        mymap[a] = 1;
        
   }

   for( it = mymap.begin() ; it != mymap.end() ; it++ ){
            
    int val = it->first;
    
    if( mymap.find(k-val) != mymap.end() ){
        
        cnt += min( it->second, mymap.find(k-val)->second );
        it->second = 0;
        
    }
    
 }
  cout<<cnt;

另一种在最好情况下采用 O(log n) 而在最坏情况下采用 O(nlog n) 正数的方法可以通过以下方式完成:

  1. 在数组中查找等于 k/2 的元素,或者如果它不存在,则找到大于 k/2 的最小值。 与此元素和所有更大元素的所有组合都会对我们感兴趣,因为当 p>= k/2 和 s>=k/2 时,p + s >= k。 数组已排序,因此可以使用经过一些修改的二进制搜索。 此步骤将花费 O(log n) 时间。
  2. 所有小于 k/2 + 大于或等于“镜像元素”(根据中值 k/2)的元素也会对我们感兴趣,因为当 p=k/2-t 和 s 时 p + s >= k >= k/2+t。 这里我们需要遍历小于 k/2 的元素并找到它们的镜像元素(二分搜索)。 如果镜像元素大于最后一个数组,则应停止循环。

例如,我们有数组 {1,3,5,8,11} 和 k = 10,所以在第一步我们将有 k/2 = 5 和对 {5,7}, {8,11}, {8 , 11}。 这些对的计数将通过公式 l * (l - 1)/2 计算,其中 l = 元素计数 >= k/2。 在我们的例子中 l = 3,所以 count = 3*2/2=3。

在 3 数的第二步中,镜像元素将为 7(5-2=3 和 5+2=7),因此对 {3, 8} 和 {3, 11} 会感兴趣。 对于 1 号镜像将是 9(5-4=1 和 5+4=9),所以 {1, 11} 是我们要寻找的。

因此,如果 k/2 < 第一个数组元素,则该算法将是 O(log n)。

对于否定,算法会稍微复杂一点,但也可以用相同的复杂度解决。

存在使用所谓的“两个指针”或“两个迭代器”方法的相当简单的O(n)方法。 关键思想是在同一个数组上运行两个迭代器(不一定是 C++ 迭代器,索引也可以),这样如果第一个迭代器指向值x ,那么第二个迭代器指向数组中小于那么的最大元素kx .

我们将增加第一个迭代器,同时我们还将更改第二个迭代器以保持此属性。 注意,随着第一个指针的增加,第二个指针对应的位置只会减少,所以每次迭代都可以从上次迭代停止的位置开始; 我们永远不需要增加第二个指针。 这就是我们如何实现O(n)时间。

代码是这样的(这个没测试过,但是思路应该很清楚)

vector<int> a; // the given array
int r = a.size() - 1; 
for (int l=0; l<a.size(); l++) {
    while ((r >= 0) && (a[r] >= k - a[l]))
        r--;
    // now r is the maximal position in a so that a[r] < k - a[l]
    // so all elements right to r form a needed pair with a[l]
    ans += a.size() - r - 1; // this is how many pairs we have starting at l
}

另一种编码可能更简单但速度稍慢的方法是使用二进制搜索的O(n log n) 对于数组的每个元素a[l] ,您可以找到最大位置r ,使得a[r]<ka[l]使用二分搜索(这与第一个算法中的r相同)。

@Drew Dormann - 感谢您的评论。

用两个指针遍历数组。 leftright
假设left是小边,从位置0 left开始,然后right移动,直到最后一次a[left]+a[right] >= k
当达到这一点时,则total_count += (a.size - right + 1)
然后你left移动一步, right需要(可能)朝它移动。 重复这个直到他们相遇。

完成后,假设他们在位置x相遇,然后totla_count += choose(2, a.size - x)

  1. 对数组进行排序 (n log n)
  2. 对于 (i = 1 到 n)
    • 从根开始
    • 如果 a[i] + curr_node >= k,向左走并匹配 = indexof(curr_nod)e
    • 否则,向右走
    • 如果 curr_node = 叶节点,则将 a[match] 之后的所有节点添加到具有 a[i] 的有效对列表中

步骤 2 也需要 O(n log n)。 for 循环运行 n 次。 在循环中,我们对每个节点执行二分搜索,即记录 n 步。 因此,算法的整体复杂度为 O (n log n)。

这应该做的工作:

void count(int A[], int n) //n being the number of terms in array
{ int i, j, k, count = 0;
  cin>>k;

  for(i = 0; i<n; i++)
   for(j = 0; j<n; j++)
     if(A[i] + A[j] >= k)
      count++ ;

  cout<<"There are "<<count<<" such numbers" ;
} 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM