【问题标题】:Find k most occurring elements in an integer array查找整数数组中出现次数最多的 k 个元素
【发布时间】:2014-06-04 01:14:31
【问题描述】:

给定一个包含可能重复条目的数组 A,找出最常出现的 k 个条目。

我的做法:

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

读取新整数时:

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

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

class Wrapper{
 boolean inHeap;
 int count;
}

这将占用 O(n+k) 空间和 O(n log k) 时间复杂度。有没有更好的方法来处理空间和/或时间复杂度。

【问题讨论】:

  • 如果您的代码有效,请尝试 Code Review。
  • 嘿 Anubian,这只是原始方法。我阅读了这个问题并在没有编写实际代码的情况下考虑了它。当我实现它时,我会将它发布在代码审查上。同时,我希望专家对我的方法发表意见。谢谢!
  • 好吧...看起来不错...
  • Refer this 答案,你已经用整数替换字符

标签: java arrays algorithm


【解决方案1】:

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


跳过堆并创建 HashMap。

创建 HashMap 后,您可以对其进行迭代并将所有元素放入一个数组中。

然后您可以在数组上运行selection algorithm 例如quickselect 以获取k-th 元素,以及从那里获取第一个k 元素(通过快速选择提取第一个k 元素的扩展名相当琐碎,或者您可以再次迭代以获取它们)。

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

如果需要排序,预计运行时间为O(n)O(n + k log k)

空间复杂度为O(n)

【讨论】:

    【解决方案2】:

    补充@Dukeling 的答案。我在下面添加了 C++ 中的代码来解释快速选择方法。

    步骤:

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

    代码:

    #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

    【讨论】:

      【解决方案3】:

      有许多不同的算法可用于确定所谓的频繁项,基于计数器和基于草图。在基于计数器的算法中,目前最好的是Space Saving(其他算法有Lossy CountFrequent)。

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

      【讨论】:

        【解决方案4】:

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

        【讨论】:

        • 我认为您误解了问题,或者我误解了您的算法。如果 x 出现 100 次,y 出现 95 次,z 出现 50 次,我们想要找到 2 个出现次数最多的项目,我们应该返回 x 和 y。使用您的算法,您只会得到 x (因为这就是列表将包含的全部内容)。
        • 我确实误会了——我以为只寻找最常见的元素。但是,使用 MultiMap 而不是 int 和 List 可能会出现相同的主体。这将允许跟踪 n 个最大的事件。
        • 然而,随着 k 大小的增加(朝向唯一值的数量),这种方法的用处会降低
        【解决方案5】:

        我同意堆会使它复杂化。您可以简单地对数组进行合并排序(O(k log k)time),然后在创建 HashMap(O(n)time)后遍历数组。总运行时间O(n + k*log(k)) = O(k*log(k))

        【讨论】:

        • 对原始数组的合并排序将是 O(n log n) (因此问题中的解决方案优于此)。在创建哈希映射后你会做什么(哈希映射不会按原样为您提供 k 最常出现的元素)?
        • 嗯,HashMap 旨在包含值的出现次数,然后按值(即出现次数)对 HashMap 进行排序。这应该给出解决方案,虽然看起来你是对的......这并没有超过问题中的解决方案。
        【解决方案6】:

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

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

        【讨论】:

        • 存在 O(N) 时间复杂度方法
        【解决方案7】:
        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
        }
        

        }

        【讨论】:

        • 添加一些解释,说明此答案如何帮助 OP 解决当前问题
        【解决方案8】:

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

            import java.util.Arrays;
            import java.util.HashMap;
            import java.util.LinkedHashMap;
            import java.util.Map;
        
            public class MaxRepeating
            {
                static void maxRepeating(int arr[], int n, int k)
                {
                    Map<Integer,Integer> map=new HashMap<Integer,Integer>();
                    // increment value in map if already present
                    for (int i = 0; i< n; i++){
                        map.put(arr[i], map.getOrDefault(arr[i], 0)+1);
        
                    }
                    map.entrySet().stream()
                            .sorted(Map.Entry.<Integer, Integer>comparingByValue().reversed())
                               .limit(k).forEach(System.out::println);
        
                }
        
                /*Driver function to check for above function*/
                public static void main (String[] args)
                {
        
                    int arr[] = {7, 10, 11, 5, 2, 5, 5, 7, 11, 8, 9};
                    int n = arr.length;
                    int k=4;
                    maxRepeating(arr,n,k);
                }
            }
        

        【讨论】:

          猜你喜欢
          • 2011-04-17
          • 2021-01-13
          • 2013-05-01
          • 1970-01-01
          • 2013-06-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-04-29
          相关资源
          最近更新 更多