【问题标题】:c++ - Solution to 2-sum using unordered_mapc++ - 使用 unordered_map 解决 2-sum
【发布时间】:2017-02-26 03:40:25
【问题描述】:

好的,我正在尝试解决 C++ 中的2-SUM 问题。给定一个包含任意顺序的 1000000 个数字的文件,我需要确定是否存在总和为 t 的整数对,其中 t is each of [-10000, 10000]。所以这基本上是2-SUM 问题。

所以,我用 C++ 编写了我的解决方案,其中我使用 unordered_map 作为我的哈希表。我确保哈希表上的load 较低。但这仍然需要1hr 15mins 才能完成(成功)。现在,我想知道它是否应该那么慢。进一步降低负载因子并没有带来任何显着的性能提升。

我不知道在哪里可以优化代码。我尝试了不同的负载因子,没有帮助。这是来自 MOOC 的问题,人们已经能够使用相同的哈希表方法在大约 30 分钟内完成这项工作。任何人都可以帮助我更快地编写此代码。或者至少给出代码可能会变慢的提示。

这里是代码 -

#include <iostream>
#include <unordered_map>
#include <fstream>

int main(int argc, char *argv[]){
    if(argc != 2){
        std::cerr << "Usage: ./2sum <filename>" << std::endl;
        exit(1);
    }

    std::ifstream input(argv[1]);
    std::ofstream output("log.txt");
    std::unordered_map<long, int> data_map;
    data_map.max_load_factor(0.05);

    long tmp;
    while(input >> tmp){
        data_map[tmp] += 1;
    }

    std::cerr << "input done!" << std::endl;
    std::cerr << "load factor " << data_map.load_factor() << std::endl;

    //debug print.
    for(auto iter = data_map.begin(); iter != data_map.end(); ++iter){
        output << iter->first << " " << iter->second << std::endl;
    }

    std::cerr << "debug print done!" << std::endl;

    //solve
    long ans = 0;

    for(long i = -10000; i <= 10000; ++i){
        //try to find a pair whose sum = i.

        //debug print.
        if(i % 100 == 0)
            std::cerr << i << std::endl;

        for(auto iter = data_map.begin(); iter != data_map.end(); ++iter){
            long x = iter->first;
            long y = i - x;

            if(x == y)
                continue;

            auto search_y = data_map.find(y);
            if(search_y != data_map.end()){
                ++ans;
                break;
            }
        }
    }

    std::cout << ans << std::endl;

    return 0;
}

【问题讨论】:

  • 嗯,1M的数字范围是多少?
  • @thecoder 没有指定这样的范围。但它们很大——大约 10^11。
  • 当您说:t is each of [-10000, 10000] 时,您的意思是“正好 +10000 或 -10000”还是“在 [-10000, 10000] 范围内”?请问两者中的哪一个。
  • t 采用[-10000, 10000] 范围内的所有值
  • 我想我已经做到了最短时间。请检查我的答案中的代码,让我知道它在您的数据集上的表现。

标签: c++ hashtable unordered-map


【解决方案1】:

您可以提前为unordered 地图预留空间。它应该会提高性能

【讨论】:

  • 好的,我认为这不会产生和可观察到的性能变化,因为我提到的 1 小时 15 分钟时间不包括输入时间。也可以在较小的输入上运行它,没有实质性的变化。
【解决方案2】:

先对数组进行排序,然后对数组中的每个元素进行排序,使用二分法查找使其更接近 -10000 的数字并继续“正确”直到总和 +10000

这样可以避免遍历数组 20000 次。

【讨论】:

  • 这将是一个较慢的解决方案。您提出的解决方案的复杂性是 O(nlgn),而我的解决方案在平均 O(n) 时间内工作 - 考虑到正确实施的哈希表并且因为我使用的是 STL,这应该是正确的。
  • 为避免接下来 20 000 次执行的算法进行一次“预计算”可能是一件好事,即使它的复杂性更差。尝试计算对该数组进行排序并将其与您的 1h 执行进行比较所需的实际时间。然后,您将能够决定是否值得。
【解决方案3】:

在所有和的概率相同的统一集合上,以下内容将在几秒钟内完成。否则,对于任何缺失的总和,在我的笔记本电脑上需要大约 0.75 秒来检查缺失的总和。

与 OP 的代码相比,该解决方案有一个小的改进:检查重复并消除它们。

然后它通过蒙特卡洛启发式方法打开:对于大约 1% 的总数,从集合中随机选择一个并搜索 [minSum, maxSum] 范围内的所有总和,可以将其中一个项作为随机选择编号和其余部分。这将预先填充sums 集合...比如说...“可以轻松找到的总和”。在我的测试中,使用仅在 -10M 和 10M 之间随机生成的 1M 数字,这是一个必要的步骤,需要几秒钟。

对于病态数字分布,其中一些总和值缺失(或未通过随机启发式找到),第二部分对未找到的sum 值使用有针对性的详尽搜索,非常与 OP 中的解决方案相同。


random/Monte Carlo heuristic 的额外解释(针对@AneeshDandime 的评论):

虽然我现在还没有完全理解

嗯,很简单。可以这样想:天真的方法是将所有输入值成对相加,但只保留 [-10k, 10k] 中的和。然而,它非常昂贵(O [N ^ 2])。一个直接的改进是:选择一个值v0,然后确定哪些其他v1 值有机会给出 [-10k, 10k] 范围内的总和。如果输入值是排序的,那就更简单了:只需要在[-10k-v0, 10k-v0]中选择v1-s即可;一个很好的改进,但如果你把它作为唯一的方法,详尽的搜索仍然是 O(log2(N)N[-10k, 10k])。
但是,这种方法仍然有其价值:如果输入值是均匀分布的,它会很快用最常见的值填充 known sums 集合(并花剩下的时间尝试查找不常见或缺失的 sum 值)。
为了大写,而不是使用这个直到最后,可以继续进行有限数量的步骤,希望填充大部分总和。之后,我们可以切换焦点,进入'有针对性的搜索sum值',但只针对这一步没有找到的sum值。


[已编辑:上一个错误已更正。现在算法对于输入中多次出现或单次出现的值是稳定的]

#include <algorithm>
#include <vector>
#include <random>
#include <unordered_set>
#include <unordered_map>


int main() {
  typedef long long value_type;

  // +++++++++++++++++++++++++++++++++++++++++++++++++++++++
  // substitute this with your input sequence from the file
  std::random_device rd;
  std::mt19937 gen(rd());
  std::uniform_int_distribution<value_type> initRnd(-5500, 10000000);

  std::vector<value_type> sorted_vals;


  for(ulong i=0; i<1000000; i++) {
    int rnd=initRnd(gen);
    sorted_vals.push_back(rnd);
  }
  std::cout << "Initialization end" << std::endl;
  // end of input
  // +++++++++++++++++++++++++++++++++++++++++++++++++++++++

  // use some constants instead of magic values
  const value_type sumMin=-10000, sumMax=10000;

  // Mapping val->number of occurrences
  std::unordered_map<value_type, size_t> hashed_vals;

  for(auto val : sorted_vals) {
    hashed_vals[val]=hashed_vals[val]++;
  }

  // retain only the unique values and sort them
  sorted_vals.clear();
  for(auto val=hashed_vals.begin(); val!=hashed_vals.end(); ++val) {
    sorted_vals.push_back(val->first);
  }
  std::sort(sorted_vals.begin(), sorted_vals.end());


  // Store the encountered sums here
  std::unordered_set<int> sums;

  // some 1% iterations, looking at random for pair of numbers which will contribute with
  // sum in the [-10000, 10000] range, and we'll collect those sums.
  // We'll use the sorted vector of values for this purpose.
  // If we are lucky, most of the sums (if not all) will be already filled in
  std::uniform_int_distribution<size_t> rndPick(0, sorted_vals.size());
  size_t numRandomPicks=size_t(sorted_vals.size()*0.1);
  if(numRandomPicks > 75000) {
    numRandomPicks=75000;
  }
  for(size_t i=0; i<numRandomPicks;i++) {
    // pick a value index at random
    size_t randomIx=rndPick(gen);
    value_type val=sorted_vals[randomIx];

    // now search for the values between -val-minSum and -val+maxSum;
    auto low=std::lower_bound(sorted_vals.begin(), sorted_vals.end(), sumMin-val);
    if(low==sorted_vals.end()) {
      continue;
    }
    auto high=std::upper_bound(sorted_vals.begin(), sorted_vals.end(), sumMax-val);
    if(high==sorted_vals.begin()) {
      continue;
    }
    for(auto rangeIt=low; rangeIt!=high; rangeIt++) {
      if(*rangeIt!=val || hashed_vals[val] > 1) {
        // if not the same as the randomly picked value
        // or if it is the same but that value occurred more than once in input
        auto sum=val+*rangeIt;
        sums.insert(sum);
      }
    }
    if(sums.size()==size_t(sumMax-sumMin+1)) {
      // lucky us, we found them all
      break;
    }
  }

  // after which, if some sums are not present, we'll search for them specifically
  if(sums.size()!=size_t(sumMax-sumMin+1)) {
    std::cout << "Number of sums still missing: "
              << size_t(sumMax-sumMin+1)-sums.size()
              << std::endl
    ;
    for(int sum=sumMin; sum<=sumMax; sum++) {
      if(sums.find(sum)==sums.end()) {
        std::cout << "looking for sum: " << sum ;
        // we couldn't find the sum, so we'll need to search for it.
        // We'll use the unique_vals hash map this time to search for the other value
        bool found=false;
        for(auto i=sorted_vals.begin(); !found && i!=sorted_vals.end(); ++i) {
          value_type v=*i;
          value_type other_val=sum-v;
          if(  // v---- either two unequal terms to be summed or...
               (other_val != v || hashed_vals[v] > 1) // .. the value occurred more than once
            && hashed_vals.find(other_val)!=hashed_vals.end() // and the other term exists
          ) {
            // found. Record it as such and break
            sums.insert(sum);
            found=true;
          }
        }
        std::cout << (found ? " found" : " not found") << std::endl;
      }
    }
  }
  std::cout << "Total number of distinct sums found: " << sums.size() << std:: endl;
}

【讨论】:

  • 呃,我刚刚运行它,它给出了错误的答案。让我重述问题陈述,以防不清楚。我需要在区间[-10000, 10000] 中找到目标值t 的数量,使得在给定的数据集中存在两个满足x+y = t 的不同数字x,y。如果你做对了,那么我不明白解决方案(加上它给出了错误的答案)。
  • 解决方案将总和在[-10000, +10000] 中的对数加起来。您是否需要[-10000, +10000] 中至少存在一对与所选值之和的值的数量?
  • @Adrain 我需要 [-10000, 10000] 中的值的数量,以便数据集中存在一对总和为该值的对。
  • @AneeshDandime 只是为了确保:对于 [-10000, 10000] 中t 的每个值,您不需要实现t 总和的所有对数,但是只有这样的一对存在?
  • 是的,我只需要查找是否存在这样的对。对数不是必需的。
猜你喜欢
  • 2014-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多