有效地计算给定范围内每个元素的出现次数

时间:2015-08-15 11:17:04

标签: algorithm

所以我有一些像这样的范围:

2 4
1 9
4 5
4 7

为此,结果应为

1 -> 1
2 -> 2
3 -> 2
4 -> 4
5 -> 3  
6 -> 2
7 -> 2
8 -> 1
9 -> 1

天真的方法是循环遍历所有范围,但效率非常低,最坏的情况需要O(n * n)

可能在O(n)或O(log(n))

中的有效方法是什么

4 个答案:

答案 0 :(得分:4)

这是O(n)中的解决方案:

理由是在a中添加范围[a,b]为+1,在b之后添加-1。然后,在添加所有范围之后,计算该数组的累计总和并显示它。

如果你需要在添加值时执行查询,更好的选择是使用二进制索引树,但你的问题似乎并不需要这样,所以我把它留了出来。

#include <iostream>
#define MAX 1000
using namespace std;

int T[MAX];

int main() {
    int a, b;
    int min_index = 0x1f1f1f1f, max_index = 0;
    while(cin >> a >> b) {
        T[a] += 1;
        T[b+1] -= 1;
        min_index = min(min_index, a);
        max_index = max(max_index, b);
    }

    for(int i=min_index; i<=max_index; i++) {
        T[i] += T[i-1];   
        cout << i << " -> " << T[i] << endl;
    }
}

更新:基于גלעדברקן的“挑衅”(在很好的意义上),您也可以在O(n log n)中执行此操作:

#include <iostream>
#include <map>
#define ull unsigned long long
#define miit map<ull, int>::iterator
using namespace std;

map<ull, int> T;

int main() {
    ull a, b;
    while(cin >> a >> b) {
        T[a] += 1;
        T[b+1] -= 1;
    }

    ull last;
    int count = 0;
    for(miit it = T.begin(); it != T.end(); it++) {
        if (count > 0)
            for(ull i=last; i<it->first; i++)
                cout << i << " " << count << endl;
        count += it->second;
        last = it->first;
    }
}

此解决方案的优点是能够支持具有更大值的范围(只要输出不是那么大)。

答案 1 :(得分:1)

解决方案非常简单:

  1. 生成两个列表,其中包含范围的所有起始和结束索引的索引并对其进行排序。
  2. 为覆盖当前索引的范围数生成计数器。从任何范围的第一个项开始,并将所有数字迭代到任何范围内的最后一个元素。现在,如果索引是起始索引列表的一部分,我们将1添加到计数器,如果它是结束索引的元素,我们从计数器中减去1。
  3. 实现:

    vector<int> count(int** ranges , int rangecount , int rangemin , int rangemax)
    {
        vector<int> res;
    
        set<int> open, close;
    
        for(int** r = ranges ; r < ranges + sizeof(int*) * rangecount ; r++)
        {
            open.add((*r)[0]);
            close.add((*r)[1]);
        }
    
        int rc = 0;
    
        for(int i = rangemin ; i < rangemax ; i++)
        {
            if(open.count(i))
                ++rc;
    
            res.add(rc);
    
            if(close.count(i))
                --rc;
        }
    
        return res;
    }
    

答案 2 :(得分:1)

保罗的答案仍然来自&#34;任何范围内的第一个项目,并将所有数字上的[s]迭代到任何范围内的最后一个元素。&#34;但是,我们可以聚合重叠计数?例如,如果我们有三个(或说很多)重叠范围[(2,6),[1,6],[2,8],则(2,6)部分可能仅依赖于范围数,如果我们是用重复[(1),3(2,6),(7,8)])标记重叠。

使用二分搜索(一次用于开始,第二次用于每个间隔的结束),我们可以在O(n * log m * l)时间内分割间隔并汇总计数,其中n是我们的数量给定范围,m是总范围内的结果组数,l随特定重叠所需的不相交更新数(已在该范围内的组数)而变化。请注意,在任何时候,我们只需将排序列表分组为带有标记计数的间隔。

2 4
1 9
4 5
4 7

=>

(2,4)
(1),2(2,4),(5,9)
(1),2(2,3),3(4),2(5),(6,9)
(1),2(2,3),4(4),3(5),2(6,7),(8,9)

答案 3 :(得分:0)

所以你希望输出是一个数组,其中每个元素的值是包含它的输入范围的数量?

是的,显而易见的解决办法是为每个范围增加范围内的每个元素。

如果您按开始(主要),结束(辅助)排序输入范围,我认为您可以提高效率。因此,对于32位开始和结束,start:end可以是64位排序键。实际上,只需按start排序就可以了,我们无论如何都需要对end进行不同的排序。

然后你可以看到你为一个元素输入了多少个范围,并且(有一个范围结束的节点)看看你已经离开了多少个。

# pseudo-code with possible bugs.
# TODO: peek or put-back the element from ranges / ends
 # that made the condition false.

pqueue ends;    // priority queue
int depth = 0;  // how many ranges contain this element
for i in output.len {
    while (r = ranges.next && r.start <= i) {
        ends.push(r.end);
        depth++;
    }
    while (ends.pop < i) {
        depth--;
    }
    output[i] = depth;
}
assert ends.empty();

实际上,我们可以将开始和结束分别分为两个独立的优先级队列。没有必要在飞行中建立pqueue。 (对整数数组进行排序比通过一个结构成员对结构数组进行排序更有效,因为您不必复制尽可能多的数据。)