【问题标题】:Efficient algorithm for counting number of pairs of identical elements in an array计算数组中相同元素对数的有效算法
【发布时间】:2014-11-06 05:31:29
【问题描述】:

例如,

int num[] = {1, 5, 3, 12, 5, 1, 4};
int len = 7;
int count = 0;

(假设数组中相同的元素不超过 2 个)
那我就做吧

for(int i=0; i<len-1; i++) {
  for(int j=i+1; j<len; j++) {
    if(num[i] == num[j]) {
      count++;
    }
  }
}  

那么计数将是 2。

但是这个算法的效率是O(N^2)。
有没有更有效的方法?
提前谢谢!

【问题讨论】:

  • 如果你可以对数组进行排序,总时间将是 O(n log n) - 排序是 n log n,找到相同的元素将是线性的。
  • 是否允许负值?数组中 int 的最大/最小值是多少?

标签: c arrays performance algorithm


【解决方案1】:

比O(n log n) 排序更快,使用hash table。它的构造是线性的或O(n);每次插入时,当您遍历输入数组时,您可以测试(使用常量,O(1) 成本)是否已经在哈希表中存在该键 - 即,“对”。当你找到这个“匹配”的情况时,你增加一个计数器。一旦你完成了你的数组,你的计数器就会告诉你答案。

【讨论】:

  • 如何处理碰撞?如果您假设哈希表中有 n 个插槽并且是一个完美的哈希,那么是的,您有 O(n)。但是如果你使用一个固定大小的——k 说——哈希表,那么每个槽中将有 n/k 的平均值,并且插入一个元素将是 O(n/k) = O(n),所以 O (n*n) 只是为了建表。
  • 一个足够大的哈希表(比如初始数组的大小)应该使冲突非常少见(除了一个坏的哈希函数)。要处理散列到相同键的元素的桶,可以使用列表或对数访问数据结构(堆、平衡树......)。后者对于练习来说似乎有点矫枉过正,但保证了 O(n.ln n) 时间。
  • 哈希表中的命中只给出了重复的候选者。然后,您将焦点项目与表格中的项目进行比较,以最终判断它们是否重复。
【解决方案2】:

你可以试试这个方法:

#define MAX 99999   // consider your array have largest elemnt < 99999 and >=0
    int main () {
    int num[] = {1, 5, 3, 12, 5, 1, 4,3};
    int len = 8;
    int count[MAX+1u] = {0};
    int i,pair=0;
    for( i=0;i<len;i++){
         count[num[i]]++;   // here count the frequecy of each number
    }
    for(i=0;i<MAX;i++){
     if(count[i]>1){               // if frequecy is > 1      
    printf("%d occurs %d times \n",i,count[i]);
    pair++;         // increment pair
    }

    }
    printf("%d pairs ",pair);   // print pair
       return 0;
    }

【讨论】:

  • @chux 我当时正在更新。是的,当然。
  • 使用此解决方案的内存使用量会非常大。如果您知道您从“小”区间获取整数,这当然是最好的。
  • 你应该用一点“免责声明”来编辑你的答案。当输入数据集增长过多时,哈希表解决方案会变得更好(关于内存使用)。您的实现还假设数组中没有负整数。
  • @Rerito 碰撞怎么样。假设您的数据很少,那么您将如何获得唯一的哈希键。
  • 使用桶,使用适当的数据结构实现会给你一个 O(n.ln n) 时间。当然,大多数情况都非常接近线性时间。
【解决方案3】:

基本上,核心思想保持不变:您必须计算输入数组中每个元素的出现次数。

关键问题是如何实现这个计数过程。您的解决方案是完全有效的,但正如您所感受到的,它可以改进。 您收到一条评论,建议对数组进行排序,然后对其执行扫描以计算对数:O(n.ln n)。

您也可以使用哈希表,正如@AlexReynolds 回答所建议的那样。但是你必须处理冲突,因为不同的整数可以散列到同一个键。为此,您可以为每个键使用一个存储桶。这个桶会存储每个散列到它的键的整数,加上该整数的出现次数。

如何实现这些桶:

  • 使用 列表 ?如果您有几次碰撞,这就足够了。
  • 使用 堆(如二进制堆):对数访问和插入,但大小固定,如果发生太多冲突,您可能需要重新分配。如果您不低估冲突的数量,这很好,但如果您高估它会增加内存使用量。
  • 使用平衡二叉树:对数查找和插入,但由于指针跟踪在“内部”进行,空间开销。但是在这里,您可以避免 堆 引起的估计问题。

一旦你的哈希表建立起来,每个元素出现在你的数组中,你必须计算对。但是您可以在填充数据结构时保留一个计数器,以避免执行这种额外的扫描。这很简单。以下是用于使用从数组中获取的元素 e 更新表格的基本操作:

  1. Hash e : 我们得到一个Hash key k
  2. 获取哈希键k的桶:b
  3. 查找桶以查看 e 是否在其中:
    • 如果是这样:增加元素出现计数器。 如果计数器为 2,则增加对计数器,如果计数器为 3,则减少对计数器。
    • 如果不是:将元素添加到存储桶中,并将出现计数器设置为 1
  4. 在 e 中获取输入数组中的下一个元素并重复直到数组被完全扫描。

在每次 counter update 时执行一个 if 语句是否比最后执行一个简单的检查更快?我不确定,请注意您可以同时测试两者。

让k 为哈希表中的槽数。使用均匀分布的散列函数,您将在每个插槽中获得 n/k 个元素。这会导致列表的 n²/k 时间复杂度,即 O(n²) ... 但是 如果 k 接近 n 你真正接近线性时间。

堆/树桶也是如此,除了最后你会得到 O(n. ln n) 的渐近复杂度。如果 k 足够大,那么您将再次接近线性时间。

【讨论】:

    【解决方案4】:

    你不能使用普通的哈希表。我使用了动态编程和哈希表。

    你应该像这样设置你的哈希表:

    [数组的值,重复次数,相同对的数量]

    例如,我们可以在数组A = [3, 3, 3, 3, 3] 上运行我的算法。

    我们遍历数组。对于 A 中的第一个数字,将新行插入到哈希表中

      3,0,0 /* A[i], the number of repetition of 3 so far (Rep[i]),
                        the number of identical pair so far ( Iden[i]). */
    

    然后在 A 中的第二个 3:

     3,1,1 
    

    A 中的第三个 3:

     3,2,3 
    

    A 区第 4 3 位:

     3,3,6 
    

    6 是该数组中相同对的数量。一般来说,我们可以通过以下公式计算相同对的数量:

    Iden[i] = Rep[i] + Iden[i-1]

    这是一个 C# 代码示例:

         public static int solution(int[] A)
        {
             int identical = 0;
            Dictionary<int, KeyValuePair<int, int>> dic = new Dictionary<int, KeyValuePair<int, int>>(); /* A[i], the number of repetition of 3 so far (Rep[i]),
                        the number of identical pair so far ( Iden[i]). */
            for (int i = 0; i < A.Length; i++)
            {
                if (!dic.ContainsKey(A[i]))
                    dic.Add(A[i], new KeyValuePair<int, int>(0,0));
                else
                { 
                   KeyValuePair<int,int> valDic = dic[A[i]];
    
                    KeyValuePair<int, int> newVal;
                    if (valDic.Key < 1)
                        newVal = new KeyValuePair<int, int>(1, 1);
                    else
                    {
                        int preIdenticalPair = valDic.Value;
                        int preReptation = valDic.Key;
                        int newRepetation = ++preReptation;
                        int newIdenticalPair = preIdenticalPair + newRepetation;
                        newVal = new KeyValuePair<int, int>(newRepetation, newIdenticalPair);
                    }
    
                    dic[A[i]] = newVal;
                }
            }
    
            //summation of all identical pairs
            foreach (KeyValuePair<int, KeyValuePair<int, int>> pair in dic)
                identical += pair.Value.Value;
            return identical;
        }
    

    【讨论】:

    • 问题被标记为 [c]
    猜你喜欢
    • 2018-03-02
    • 2015-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-12
    • 1970-01-01
    相关资源
    最近更新 更多