【问题标题】:More efficient way of counting the number of values within an interval?计算区间内值数量的更有效方法?
【发布时间】:2014-12-17 05:45:25
【问题描述】:

我想确定在我的每个给定区间(很多)中有多少个输入数组(最多 50000 个)。

目前,我正在尝试用这个算法来做,但是太慢了:

示例数组:{-3, 10, 5, 4, -999999, 999999, 6000}
示例间隔:[0, 11](含)

  1. 排序数组 - O(n * log(n))。 (-999999, -3, 4, 5, 10, 6000, 999999)
  2. 查找 min_index:array[min_index] >= 0 - O(n)。 (例如,min_index == 2)。
  3. 查找 max_index:array[max_index] <= 11 - O(n)。 (例如,max_index == 4)。
  4. 如果两个索引都存在,则 Result == right_index - left_index + 1(例如,Result = (4 - 2 + 1) = 3)。

【问题讨论】:

  • 为什么不直接把数组从头到尾遍历一遍,直接计数呢? O(n)
  • @Deduplicator,我有很多间隔。
  • 当数组排序后,你可能会在对数时间内得到min_index
  • @Jarod42:仍然受排序约束。
  • 数组元素的最大值和最小值是多少?

标签: c++ arrays algorithm sorting search


【解决方案1】:

您的想法不错,但需要修改。您应该使用binary search 找到O(lg n) 时间间隔的开始和结束。如果 n 是数组的长度并且 q 是问题的数量[a, b] 你有 O(n+q*n) 时间,使用二分搜索它是O((n + q) lg n)(来自排序数组的n lg n)。 这个解决方案的优点是简单,因为 C++ 有std::lower_boundstd::upper_bound。你可以使用std::distance。只是几行代码。

如果 q 等于 n,则此算法具有O(n lg n) 复杂度。有待改善?一点也不。为什么?因为问题相当于排序。众所周知,不可能获得更好的计算复杂度。 (通过比较排序。)

【讨论】:

  • 顺序还是由排序决定的,不够好。
  • @Tacet,我不能使用二分搜索,因为[a, b] 可能不包含在数组中。
  • @Deduplicator,是的,这是我的错,我理解我的错误。
  • @Max:如果我理解这个问题,那没关系。你应该首先找到不大于 p,并且首先大于 q。 Binsearch 还是不错的。
  • 重申我的评论:您的提案的复杂性仍然取决于排序的复杂性,其顺序为 O(n log n),OP 表示这还不够好。顺便说一句:RMQ 与这个问题有什么关系?
【解决方案2】:

有一个简单的 O(ninput*mintervals) 算法:

为了便于实施,我们使用半开区间。根据需要转换您的。

  1. 将您的区间转换为半开区间(总是更喜欢半开区间)
  2. 将所有限制保存在一个数组中。
  3. 对于输入中的所有元素
    • 对于限制数组中的所有元素
      • 如果输入小于限制,则增加计数
  4. 检查您的时间间隔并通过减去相应限制的计数来获得答案。

为了稍微提高性能,请在步骤 2 中对限制数组进行排序。

【讨论】:

  • 再次感谢您的帮助。我没有时间实现你的算法,但我认为一切都会好起来的。
  • 你可以用一个变体来实现 O((n+m)lg m)。
【解决方案3】:

为您的数字创建一个 std::map 到它们在排序数组中的索引。

根据您的示例 map[-999999] = 0, map[-3] = 1, ... map[999999] = 7。

要查找区间,请找到大于或等于最小值的最小数(使用 map.lower_bound()),并找到高于最大值的第一个数(使用 map.upper_bound())。

您现在可以从上索引中减去下索引,以在 O(log n) 中找到该范围内的元素数。

【讨论】:

  • 如果我错了,请纠正我,但这不是算法 O(n*lg n) 吗?
  • 它的 O(n lg n + q lg n)。
  • @patros,我试过这种方式。不幸的是,它比我需要的要慢。
  • 创建排序后的数组,索引为 (n*log n)。查找每个间隔计数是 O(log n)。
【解决方案4】:
typedef std::pair<int,int> interval;
typedef std::map<interval,size_t> answers;
typedef std::vector<interval> questions;

// O((m+n)lg m)
answers solve( std::vector<int>& data, questions const& qs ){
  // m = qs.size()
  // n = data.size()

  answers retval;
  std::vector<std::pair<int, size_t>> edges;
  edges.reserve( q.size()+1 );
  // O(m) -- all start and ends of intervals is in edges
  for ( auto q:qs ) {
    edges.emplace_back( q.first, 0 );
    edges.emplace_back( q.second, 0 );
  }
  // O(mlgm) -- sort
  std::sort(begin(edges),end(edges));
  edges.emplace_back( std::numeric_limits<int>::max(), 0 );
  // O(m) -- remove duplicates
  edges.erase(std::unique(begin(edges),end(edges)),end(edges));
  // O(n lg m) -- count the number of elements < a given edge:
  for(int x:data ){
    auto it = std::lower_bound( begin(edges), end(edges), std::make_pair(x,0) );
    it->second++;
  }
  // O(m)
  size_t accum = 0;
  for(auto& e:edges) {
    accum += edges.second;
    edges.second = accum;
  }
  // now edge (x,y) states that there are y elements < x.

  // O(n lg m) -- find the edge corresponding
  for(auto q:questions){
    auto low = std::lower_bound(begin(edges), end(edges),
      std::make_pair(q.first, size_t(0))
    );
    auto high = std::upper_bound(begin(edges), end(edges),
      std::make_pair(q.second, size_t(0))
    }
    size_t total = high->second - low->second;
    answers.emplace(q,total);
  }
  return answers;
}

O((n+m)lg m),其中 n 是整数计数,m 是区间数,x 是每个区间重叠的平均区间数。

【讨论】:

  • O((n+m) lg m),好的,但是这里的 n 很小,所以 m 可能很大。 O((n+m) lg n) 更容易编写并且可能更快。进一步O((n+m) lg n)可以在线,需要更少的内存。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-01-16
  • 2020-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多