假设k <= n*(n-1)/2。如果不是,则无法回答。
我们可以使用二分查找来解决这个问题。我们对可能的对总和进行二分搜索。
这里,
低 = 可能的最小总和,即low = difficulties[0] + difficulties[1],
高 = 可能的最大总和,即high = difficulties[n-1] + difficulties[n-2]。
所以,mid = low + (high - low)/2
现在,在二进制搜索的 1 次迭代中,我们将计算索引对 (i, j), i < j 使得 difficulties[i] + difficulties[j] <= mid。如果计数小于 k,low = mid + 1 否则如果计数 >= k,high = mid。现在,这一迭代可以在O(NlogN) 中完成。
您可以在 (high - low) > 1 之前执行此操作。因此,每次您将搜索空间减少一半。因此,总时间复杂度为 O(N*logN*logMaxsum),对于 N <= 1e6 和 difficulties[i] <= 1e18,运行时间不到 1 秒。
现在 high 可以等于 low 或者 high 可以等于 low +1。答案可以等于low 或high。现在,您只需要解决低是否是可能的总和的问题(可以在O(N) 中使用散列轻松解决)和否。成对的索引(i, j), i < j 使得difficulties[i] + difficulties[j] <= low。如果两个条件都满足,那么这就是你的答案。如果不是,那么答案就是高。
运行示例测试用例:
让我们考虑一下初始数组,difficulties = [1, 4, 3, 2, 4] 和 k = 6。
您首先对花费我们O(NlogN) 的数组进行排序。排序后difficulties = [1, 2, 3, 4, 4]
所有n*(n-1)/2 = 10 对将是:
(1 + 2) => 3
(1 + 3) => 4
(1 + 4) => 5
(1 + 4) => 5
(2 + 3) => 5
(2 + 4) => 6
(2 + 4) => 6
(3 + 4) => 7
(3 + 4) => 7
(4 + 4) => 8
这更像是一个伪代码来理解逻辑的运行。
sort(difficulties)
low = difficulties[0] + difficulties[1] // Minimum possible sum
high = difficulties[n-1] + difficulties[n-2] // Maximum possible sum
while(high - low > 1){
mid = low + (high - low)/2
count = all pairs (i, j) and i < j such that difficulties[i] + difficulties[j] <= mid.
if(count < k){
low = mid +1
}else{
high = mid
}
}
Iteration 1:
low = 3
high = 8
mid = 5
count = 5 [(1 + 2), (1 + 3), (1 + 4), (1 + 4), (2 + 3)]
count < k, so low = mid + 1 = 6
----------
Iteration 2:
low = 6
high = 8
mid = 7
count = 9 [(1 + 2), (1 + 3), (1 + 4), (1 + 4), (2 + 3), (2 + 4), (2 + 4), (3 + 4), (3 + 4)]
count >= k, so high= mid = 7
现在,while 循环从 high(7) - low(6) = 1 开始停止。
现在,您需要检查 sum 6 是否可能以及所有 (i, j) >= k 的计数。如果它是那么低就是答案,在这种情况下它是正确的。因此,对于 k = 6,答案 = 6。
要实现计数,您可以再次进行二进制搜索。选择第一个索引为i,然后您只需在数组[i+1, n-1] 中找到mid - difficulties[i] 的上限。然后将 i 增加 1 并重复相同的操作。因此,您遍历每个索引 0 <= i <= n-1 并在 [i+1, n-1] 的数组搜索空间中找到它的上限,并且每次迭代都采用 O(NlogN)。
要了解为什么最后一步是检查低或高是否是可能的总和,请尝试为数组 difficulties = [10, 40, 30, 20, 40] 运行算法。
更新:
下面是完整的工作代码,时间复杂度为O(N*logN*logMaxsum),包括用于清晰理解逻辑的cmets。
#include<bits/stdc++.h>
#define ll long long int
using namespace std;
void solve();
int main(){
solve();
return 0;
}
map<int, int> m;
vector<ll> difficulties;
ll countFunction(ll sum){
/*
Function to count all the pairs of indices (i, j) such that
i < j and (difficulties[i] + difficulties[j]) <= sum
*/
ll count = 0;
int n = (int)difficulties.size();
for(int i=0;i<n-1;i++){
/*
Here the outer for loop means that if I choose difficulties[i]
as the first element of the pair, then the remaining sum is
m - difficulties[i], so we just need to find the upper_bound of this value
to find the count of all pairs with sum <= m.
upper_bound is an in-built function in C++ STL.
*/
int x= upper_bound(difficulties.begin(), difficulties.end(), sum-difficulties[i]) - (difficulties.begin() + i + 1);
if(x<=0){
/*
We break here because the condition of i < j is violated
and it will be violated for remaining values of i as well.
*/
break;
}
//cout<<"x = "<<x<<endl;
count += x;
}
return count;
}
bool isPossible(ll sum){
/*
Hashing based solution to check if atleast 1 pair with
a particular exists in the difficultiesay.
*/
int n = (int) difficulties.size();
for(int i=0;i<n;i++){
/*
Choosing the ith element as first element of pair
and checking if there exists an element with value = sum - difficulties[i]
*/
if(difficulties[i] == (sum - difficulties[i])){
// If the elements are equal then the frequency must be > 1
if(m[difficulties[i]] > 1){
return true;
}
}else{
if(m[sum - difficulties[i]] > 0){
return true;
}
}
}
return false;
}
void solve(){
ll i, j, n, k;
cin>>n>>k;
difficulties.resize(n);
m.clear(); // to run multiple test-cases
for(i=0;i<n;i++){
cin>>difficulties[i];
m[difficulties[i]]++;
}
sort(difficulties.begin(), difficulties.end());
// Using binary search on the possible values of sum.
ll low = difficulties[0] + difficulties[1]; // Lowest possible sum after sorting
ll high = difficulties[n-1] + difficulties[n-2]; // Highest possible sum after sorting
while((high-low)>1){
ll mid = low + (high - low)/2;
ll count = countFunction(mid);
//cout<<"Low = "<<low<<" high = "<<high<<" mid = "<<mid<<" count = "<<count<<endl;
if (k > count){
low = mid + 1;
}else{
high = mid;
}
}
/*
Now the answer can be low or high and we need to check
if low or high is a possible sum and does it satisfy the constraints of k.
For low to be the answer, we need to count the number of pairs with sum <=low.
If this count is >=k, then low is the answer.
But we also need to check whether low is a feasible sum.
*/
if(isPossible(low) && countFunction(low)>=k){
cout<<low<<endl;
}else{
cout<<high<<endl;
}
}