【问题标题】:Improving a solution改进解决方案
【发布时间】:2021-02-11 13:06:48
【问题描述】:

任务的描述是这样的:
我们有 n 个数字,我们必须找到数组中所有对的唯一和的数量。
例如:

3 2 5 6 3  
The sums of all the pairs(non-repeated) are 5 9 8 6 8 7 5 11 9 8  
Unique are 5 9 8 6 7 11  
Therefore output is 6  

我想出了这个非常原始且耗时(即复杂性)的解决方案:

int n = 0;
    cin >> n;
    vector<int> vec(n);
    for (int i = 0; i < n; i++)
    {
        cin >> vec[i];
    }
    vector<int> sum;
    for (int i = 0; i < n; i++)
    {
        for (int j = i+1; j < n; j++)
        {
            sum.push_back(vec[i] + vec[j]);
        }
    }
    sort(sum.begin(), sum.end());
    for (int i = 0; i < sum.size()-1;)
    {
        if (sum[i] == sum[i + 1]) sum.erase(sum.begin() + i);
        else i++;
    }
    cout << endl << sum.size();

我觉得可以使用 Combinatorics 或更简单的方法来解决问题。我想了很多,什么也想不出来。所以我的要求是是否有人可以改进解决方案。

【问题讨论】:

  • 您的最后一个循环可以替换为std::unique
  • @Someprogrammerdude 嗯,这是你教给我的一件有趣的事。我会利用它。否则,不知道如何通过时间复杂度以某种方式使解决方案变得更好?我觉得for的真的占了很多
  • 此外,您可以轻松计算矢量sum 的元素数量,并在向其添加元素之前适当地保留或调整其大小。
  • 或者您可以使用std::setstd::unordered_set 代替向量。
  • @DavidPetriashvili 这几乎就是集合的定义,是的。 (不幸的是,巨大的复杂性因素是二次加法循环。如果你想要真正的改进,那就是你需要关注的地方。不幸的是,我想不出任何聪明的东西......)

标签: c++ vector combinatorics


【解决方案1】:

如上所述,如果不计算所有对的总和,您将很难做到这一点,所以我不打算处理这个问题,我只是想就高效的数据结构提出建议。

您的解决方案分析

您的代码预先添加所有内容O(n^2),然后对O(n^2 log(n)) 进行排序,然后删除重复项。但是由于您正在从向量中擦除,因此最终具有 complexity 与列表末尾的元素数呈线性关系。这意味着第二个循环将使您的算法复杂O(n^4)

您可以在不删除的情况下计算排序数组中的唯一元素

int count = 0;
for (int i = 0; i < sum.size()-1; ++i)
{
        if (sum[i] != sum[i + 1]) ++count
}

仅此更改会使您的算法变得复杂O(n^2 log n)

没有排序的替代方案。

这里是 O(n^2) 和存储的替代方案,具体取决于输入值的范围而不是向量的长度(最后一个除外)。

我正在测试 1000 个小于 0 到 10000 的元素

vector<int> vec;
for(int i = 0; i < 1000; ++i){
    vec.push_back(rand() % 10000);
}

您的实现sum_pairs1(vec)(18 秒)

int sum_pairs1(const vector<int> &vec){
    vector<int> sum;
    int n = vec.size();
    for (int i = 0; i < n; i++)
    {
        for (int j = i+1; j < n; j++)
        {
            sum.push_back(vec[i] + vec[j]);
        }
    }
    sort(sum.begin(), sum.end());
    for (int i = 0; i < sum.size()-1;)
    {
        if (sum[i] == sum[i + 1]) sum.erase(sum.begin() + i);
        else i++;
    }
    return sum.size();
}

如果您知道值总和的范围,则可以使用 bitset,有效利用内存sum_pairs2&lt;20000&gt;(vec)(0.016 秒)。

template<size_t N>
int sum_pairs2(const vector<int> &vec){
    bitset<N> seen;
    int n = vec.size();
    for (int i = 0; i < n; i++)
    {
        for (int j = i+1; j < n; j++)
        {
            seen[vec[i] + vec[j]] = true;
        }
    }
    return seen.count();
}

如果您知道最大和不是那么高(向量不是很稀疏),但您在编译时不知道可以使用向量,则可以跟踪最小值和最大值以分配最小值可能,也支持负值。

int sum_pairs2b(const vector<int> &vec){
    int VMAX = vec[0];
    int VMIN = vec[0]
    for(auto v : vec){
        if(VMAX < v) VMAX = v;
        else if(VMIN > v) VMIN = v;
    }
    vector<bool> seen(2*(VMAX - VMIN) + 1);
    int n = vec.size();
    for (int i = 0; i < n; i++)
    {
        for (int j = i+1; j < n; j++)
        {
            seen[vec[i] + vec[j] - 2*VMIN] = true;
        }
    }
    int count = 0;
    for(auto c : seen){
        if(c) ++count;
    }
    return count;
}

如果您想要一个更通用的解决方案,可以很好地处理稀疏数据sum_pairs3&lt;int&gt;(vec)(0.097 秒)

template<typename T>
int sum_pairs3(const vector<T> &vec){
    unordered_set<T> seen;
    int n = vec.size();
    for (int i = 0; i < n; i++)
    {
        for (int j = i+1; j < n; j++)
        {
            seen.insert(vec[i] + vec[j]);
        }
    }
    return seen.size();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-06
    • 2022-12-01
    • 2019-02-19
    • 1970-01-01
    相关资源
    最近更新 更多