给定一个排序数组和一个参数 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) 的正数可以这样完成:
- 在数组中查找等于 k/2 的
- 元素,或者如果不存在,则查找大于 k/2 的最小值。所有与这个元素和所有更大的元素的组合都会对我们感兴趣,因为当 p>= k/2 和 s>=k/2 时,p + s>= k。 数组是排序的,因此可以使用带有一些修改的二进制搜索。此步骤将花费 O(log n) 时间。
- 所有小于 k/2 + 大于或等于"镜像元素"的元素(根据中位数 k/2)也会对我们感兴趣,因为当 p=k/2-t 和 s>= k/2+t 时,p + s>= k。在这里,我们需要遍历小于 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,所以计数 = 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(n)
方法,使用所谓的"两个指针"或"两个迭代器"方法。关键思想是让两个迭代器(不一定C++迭代器,索引也可以)在同一个数组上运行,这样如果第一个迭代器指向值 x
,则第二个迭代器指向数组中小于 k-x
的最大元素。
我们将增加第一个迭代器,在执行此操作的同时,我们还将更改第二个迭代器以维护此属性。注意,随着第一个指针的增加,第二个指针的对应位置只会减小,所以每次迭代我们都可以从上一次迭代停止的位置开始;我们永远不需要增加第二个指针。这就是我们实现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]<k-a[l]
(这与第一种算法中的r
相同)。
@Drew Dormann - 感谢您的评论。
使用两个指针运行数组。 left
和right
.
假设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)
.
- 对数组进行排序 (n log n)
- 对于 (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 次。在循环中,我们对每个节点执行二叉搜索.log即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" ;
}
- 如何创建一个CMake变量,除非显式重写,否则使用默认值
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 为什么两个不同的未命名名称空间可以共存于一个cpp文件中
- 运行同一解决方案的另一个项目的项目
- 挂起和取消挂起一个文件DLL
- 创建一个循环,该循环将输出大于零且小于 60(不包括 60)的所有 5 的倍数
- C++:检查向量中的元素是否大于另一个具有相同索引的元素的有效方法?
- 给定一个偶数(大于2),返回两个素数,其总和等于给定数
- (也许是NP-Hard)求一个集合的子集总数,使得每个子集在与其所有元素相乘时的值都大于X
- 如何将一个数字(大于8个字节)从字符阵列转换为其ASCII表示
- 给定一个数字n,打印序列中大于或等于n的第一个数字
- 强制一个字符串大于另一个字符串
- 在C++中打开一个数字大于9的COM端口
- 在C++中获取大于或等于一个双精度的最小浮点值(以及小于或等于一的最大浮点值)
- 当我想要大于1000000的质数时,下面的质数生成器代码显示了一个错误
- 是否有一种方法可以正确地比较一个float值是否大于/小于另一个
- 我如何从一个8位整数中得到一个大于8位的值
- 如何将一个整数除以另一个大于零的数并给出余数
- 给定一个排序数组和一个参数 k,求出线性时间内大于或等于 k 的两个数字的总和
- 使用 STL 映射/集合/多重集/多重映射,如何查找大于或等于搜索键的第一个值