【问题标题】:Find the kth lowest sum of unique pair in an array查找数组中唯一对的第 k 个最低和
【发布时间】:2021-03-16 07:24:06
【问题描述】:

给定一个数字数组,每个数字代表一个问题的难度。排队的人应该选择任意两个问题来解决。这两个选择的问题应该是不同的,并且这对问题不应该被任何人先前选择。因为他们知道困难,他们会选择困难总和最小的那对。

找出站在队列中第 k 个位置的人的最小困难总和。即数组中唯一对的第 k 个最小和。

方法 1:蛮力方法(O(n2)) 计算所有可能的唯一和并将其存储在数组中,并对唯一和数组进行排序以获得第 k 个元素。

方法 2:对困难数组进行排序并选择最小元素(对于前 4 个元素,我们可以有 6 个唯一对。所以如果 k 小于或等于 6,我们可以使用排序数组中的前 4 个元素来找到最小总和)并用最小数组做了方法1。

这两种方法都没有解决超时情况。需要一种提高时间效率的解决方案。

注意:不同的问题也可以有相同的难度级别(即数组可以包含重复的数字),默认情况下不按排序顺序。

difficulties = [1,4,3,2,4]

Person comes first chooses: 1+2 = 3
2nd person: 1+3 = 4
3rd person: 1+4 (or) 1+4(since difficulty of two problems are 4) (or) 2+3 = 5
4th person: 2+3 (or) 1+4(based on the previous selection) = 5

需要的最终答案只是最小总和,而不是实际元素。

假设约束为:
2 5
1 1 9

在哪里,
N 是数组的长度
k 是人们必须选择问题的位置

【问题讨论】:

  • 只是为了清楚。如果我们有一个包含 5 个元素的数组,比如 A B C D E,那么可供选择的对是 A+B, A+C, A+D, A+E, B+C, B+D, B+E, C+D, C+E, and D+E。所以我们最多可以有 10 人排队。对吗?
  • 是的。唯一对的总数。即 n(n-1)/2 对
  • 用尝试过的方法和示例更新了问题。
  • 你能给我一些问题链接,以便我可以在回答之前尝试提交吗?另外,对于您示例中的输入数组difficulties = [1,4,3,2,4] 和“这两个问题应该不同”的语句,意味着我不能制作两对(1+4)(1+4)?您使用它们的实际元素或索引将它们视为不同?
  • @user3386109 K 可以而且很可能会大于 N

标签: arrays algorithm sorting


【解决方案1】:

假设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 <= 1e6difficulties[i] <= 1e18,运行时间不到 1 秒。

现在 high 可以等于 low 或者 high 可以等于 low +1。答案可以等于lowhigh。现在,您只需要解决低是否是可能的总和的问题(可以在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. (1 + 2) => 3
  2. (1 + 3) => 4
  3. (1 + 4) => 5
  4. (1 + 4) => 5
  5. (2 + 3) => 5
  6. (2 + 4) => 6
  7. (2 + 4) => 6
  8. (3 + 4) => 7
  9. (3 + 4) => 7
  10. (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 &lt;= i &lt;= 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;
    }
}

【讨论】:

  • 这看起来仍然比较耗时,因为迭代是基于 maxSum 的。困难[i] 可以达到 109 并且 maxSum 将是两个这样的元素的总和,其中数组的长度相对较小。
  • @Manojkumar 二进制搜索最多需要 30 次迭代。每个都涉及对 10^5 个元素的扫描。所以你最多需要几百万次操作。相比之下,k 可以达到数十亿。它可能看起来很耗时,但实际上并非如此。
  • @Manojkumar 涉及最大元素总和的日志。即使困难[i] 最高可达 1018,那么 log(1018) 也最多为 64。
  • @Manojkumar 该解决方案的规定复杂度为N * logN * logS,其中 N 是数组中的元素数,S 是 2 个元素的最大总和。所以给定 N=1e5 和 S=2e9,操作数估计为1e5 * 17 * 31 = 53e6。如果目标是在一秒钟内解决问题,这是一个相当合理的数字。
  • @risingStark 如果二进制搜索进入数组的最后一部分,那么计算对数操作可以接近 n^2 对吗?你能解释一下这部分吗
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多