在整数数组中查找k个最常出现的元素

时间:2014-06-04 01:14:31

标签: java arrays algorithm

给定具有可能重复条目的数组A,找到最常出现的k个条目。

我的方法:

创建由频率排序的大多数k元素的MinHeap。顶部元素显然是其余元素的最少发生。 创建一个HashMap来跟踪所有元素计数以及它们是否在MinHeap中。

读取新整数时:

  • 检查它是否在HashMap中:增加HashMap中的计数
  • 如果检查它是否在堆中:那么也在那里增加计数并堆积。
  • 如果没有,则与根元素计数进行比较,并删除根,以便在必要时添加。然后堆肥。

最后将MinHeap作为所需的输出返回。

class Wrapper{
 boolean inHeap;
 int count;
}

这将花费O(n + k)空间和O(n log k)时间复杂度。是否有更好的方法来做空间和/或时间复杂性。

8 个答案:

答案 0 :(得分:7)

我们可以说您的方法的空间复杂度为O(n),因为您永远不会使用超过O(2n) = O(n)个内存。


跳过堆并只创建HashMap。

在创建HashMap之后,您可以遍历它并将所有元素放在一个数组中。

然后,您可以在数组上运行selection algorithm,例如quickselect,以获取k - 元素,并从那里运行第一个k元素(要提取的扩展名)通过quickselect的第一个k元素相当简单,或者你可以再次迭代来获取它们。)

然后,如果需要,您可以对k元素进行排序。

如果需要排序,则预计会有O(n)O(n + k log k)的运行时间。

空间复杂度为O(n)

答案 1 :(得分:3)

添加@Dukeling的答案。我在下面用C ++添加了代码来解释quickselect方法。

步骤:

  1. 使用map获取每个唯一元素的频率。
  2. 执行quickselect以获取最大的第k个元素。
  3. 从0到k选择向量中的元素。
  4. 代码:

    #include <cmath>
    #include <cstdio>
    #include <vector>
    #include <iostream>
    #include <algorithm>
    #include <map>
    
    using namespace std;
    
    map<int,int> m;
    
    void swap(int *a,int *b){
        int temp=*a;
        *a=*b;
        *b=temp;
    }
    
    void printelements(vector<int> &temp,int k){
        for(int i=0;i<=k;++i){
            cout<<temp[i]<<endl;
        }
    } 
    
    int partition(vector<int> &a, int low,int high){
        int pivot = high-1;
        int i=low-1;
        for(int j=low;j<high-1;j++){
            if(m[a[j]]>=m[a[pivot]]){
                i++;
                swap(&a[i],&a[j]);
            }
        }
        i++;
        swap(&a[i],&a[pivot]);
        return i;
    }
    
    void quickselect(vector<int> &temp,int low,int high,int k){
        if(low>high){
            return ;
        }
        int pivot=partition(temp,low,high);
        if(k==pivot){
            printelements(temp,k);
            return;
        }
        else if(k<pivot){
            quickselect(temp,low,pivot,k);
        }
        else{
            quickselect(temp,pivot+1,high,k);
        }
    }
    
    void topKelements(int a[],int n,int k){
        if(k<0)return ;
        for(int i=0;i<n;i++){
            if(m.find(a[i])!=m.end()){
                m[a[i]]++;
            }
            else{
                m.insert(pair<int,int>(a[i],1));
            }
        }
        vector<int> temp;
        map<int,int>::iterator it;
        for(it=m.begin();it!=m.end();++it){
            temp.push_back(it->first);
        }
        k=min(k,(int)temp.size()-1);
        quickselect(temp,0,temp.size(),k);
    }
    
    int main() {
        int a[] = {1,2,3,4,1,1,2,3,4,4,4,1};
        int k = 2;
        topKelements(a,12,k-1);
    }
    

    输出: 1 4 2

答案 2 :(得分:1)

对于确定所谓的频繁项目,基于计数器和基于草图的任务,有许多不同的算法。在基于计数器的算法中,当前最佳算法为Space Saving(其他算法为Lossy CountFrequent)。

节省空间需要在最坏的情况下O(n)时间和k + 1个计数器在由$ n $条目组成的输入中找到k个频繁项目。

答案 3 :(得分:0)

考虑使用Map将数字映射到它的出现次数。维护一个单独的int,其中包含当前最大的任何数字计数,以及一个List,其中包含每个带有/ count / numbers的数字。 此方法将允许您在对所有值进行单次迭代后了解结果。在最坏的情况下(如果所有值都出现1次),则使用2x内存(地图和列表中都有1个条目)。即使这样也可以通过仅在单个条目出现2次时开始将项目添加到列表中来解决。

答案 4 :(得分:0)

我同意堆会使它变得复杂。您可以简单地MergeSort数组(O(k log k)时间),然后在创建HashMap(O(n)时间)后运行数组。总运行时间O(n + k*log(k)) = O(k*log(k))

答案 5 :(得分:0)

那么,在你的第2步中,如何在log(k)时间内在堆中找到一个元素?注意堆没有排序,并且在父节点上,没有办法决定要去哪个子节点。您必须迭代所有堆成员,因此总时间为O(nk)时间。

如果将堆更改为二叉搜索树(如TreeMap),则可以在log(k)时间内找到频率。但是你必须处理重复的键,因为不同的元素可以有相同的数量。

答案 6 :(得分:0)

public class ArrayProblems {
    static class Pair {
        int value;
        int count;

        Pair(int value, int count) {
           this.value = value;
           this.count = count;
       }
    }
/*
 * Find k numbers with most occurrences in the given array
 */
public static void mostOccurrences(int[] array, int k) {
    Map<Integer, Pair> occurrences = new HashMap<>();
    for(int element : array) {
        int count = 1;
        Pair pair = new Pair(element, count);
        if(occurrences.containsKey(element)) {
            pair = occurrences.get(element);
            pair.count++;
        }
        else {
            occurrences.put(element, pair);
        }
    }

    List<Pair> pairs = new ArrayList<>(occurrences.values());
    pairs.sort(new Comparator<Pair>() {
        @Override
        public int compare(Pair pair1, Pair pair2) {
            int result = Integer.compare(pair2.count, pair1.count);
            if(result == 0) {
                return Integer.compare(pair2.value, pair1.value);
            }
            return result;
        }
    });

    int[] result = new int[k];
    for(int i = 0; i < k; i++) {
        Pair pair = pairs.get(i);
        result[i] = pair.value;
    }

    System.out.println(k + " integers with most occurence: " + Arrays.toString(result));

}

public static void main(String [] arg)
{
    int[] array  = {3, 1, 4, 4, 5, 2, 6, 1};
    int k = 6;
    ArrayProblems.mostOccurrences(array, k);

    // 3 --> 1
    // 1 --> 2
    // 4 --> 2
    // 5 --> 1
    // 2 --> 1
    // 6 --> 1
}

}

答案 7 :(得分:0)

您可以使用hashmap。如果已经存在,则在地图中增加值。然后使用lambda排序结果映射,限制为k值。

user=> (parse-opts ["-m" "/etc/hosts"] cli-opts)
{:arguments [],
 :errors nil,
 :options {:menu #<java.io.File@34d63c80 /etc/hosts>},
 :summary "  -m, --menu FILE  menu file"}