查找出现次数大于n/k的重复元素 ---非多重集算法
2011-12-04 10:07
288 查看
本文是对一篇英文论文的总结:Finding Repeated Elements。想看原文,请Google之。
这个问题的简单形式是“查找出现次数大于n/2的重复元素”。我们先从简单问题开始,然后再做扩展。
1.查找出现次数大于n/2的重复元素
《编程之美》中有同样的一道题《寻找发帖水王》,具体思路是每次删除两个不同的元素,最后剩下的就是要求的元素。这个结论的证明如下:
已知:n,m是正整数,n表示数组的长度,m是出现次数大于n/2的元素的个数,即m>n/2。
需要求证的结论包括两个:
(1)我们用v表示出现次数大于n/2的元素。当删除两个不同元素,且其中有一个元素是v时,则m减小1,同时n要减小2。
求证:m-1>(n-2)/2
证明:m-1>n/2-1=(n-2)/2
(2)当删除两个不同元素,且其中有一个元素不是v时,则只需要n减小2。
求证:m>(n-2)/2 。这个结论是显然的。
代码如下:
《编程之美》的后面习题是“查找出现次数大于n/4的元素”,思路是每次删除不同的4个元素,最后剩下的3个就是候选元素,但是还要验证这3个元素是否满足条件。不再详细解释。其实《编程之美》里讲的方法就是本文后提到的“多重集”算法。
首先讲论文里的那种非多重集算法。用到的变量如下:
b[]:元素所在的数组
i:数组索引
v:保存要查找的值,就是说,当遍历过n个元素之后,v的出现次数大于n/2
c:v出现次数的上限的两倍,即v的出现次数小于等于c/2。
初始化,i=0,v=null ,c=0。
遍历b[]数组,每当遍历到b[i]时,有两种情况会发生:
(1)b[i]=v
因为b[i]=v,所以v的出现次数又增加1,为了维护c的含义,即让c/2也增加1,那么c要增加2。
(2)c=i
这一步,我们要证明b[0...i-1]中任何元素的出现次数都不超过i/2,那么必须更新v。
我们先推出前一次遍历时c和i的关系: c每次增加2,i每次增加1,为了使本次遍历时让c=i,那么在前一次遍历中,i肯定是增加了1,而c没有增加。所以在前一次遍历之前c和i的关系是c=i+1,而前一次遍历结束时c才与i 相等。
再考察前一次遍历时的情形:我们令m表示v的出现次数,那么在前一次遍历之前的情况是 m>i/2 且 m<=
c/2=(i+1)/2,则有式子:i/2<m<=(i+1)/2,注意,此时i是奇数(根据c=i+1,c是偶数),那么m只能等于(i+1)/2,即m=c/2。
而本次遍历面临的情况是c=i,在数组b[0...i-1] 中,v的出现次数是c/2,而除去v之外的其他元素的出现次数是i-m=i-c/2=i-i/2=i/2,这说明b[0...i-1]里任意一个元素的出现次数都没有超过i/2,那么我们必须更新v。最有可能的元素就是b[i],其实有可能最终b[i]也不是。
代码:
如果不存在满足条件的v,那么v的取值是最后一个数。
2.查找出现次数大于n/k的元素
给定2<=k<=n和数组b[0...n-1],我们要查找出现次数大于n/k的元素。
在问题1.中,实际上是把数组分成了两个集合,一个集合只包含一个元素v(或空),另一个集合包含出现次数小于等于n/2的元素。类似地,我们将其扩展,仍然是把数组分成两个集合,一个集合包含出现次数可能大于n/k的元素,一个集合包含出现次数小于等于n/k的元素。
相关变量声明如下:
v:出现次数可能大于n/k的元素
c:v的出现次数不超过c/2
集合t:包含的是形如(v,c)的值对。
s:对于出现次数小于等于n/k的元素,我们设其出现次数的上限为s/k。
算法包含两个阶段:首先,选出集合t;然后,验证t中的元素是否满足条件。我们在这里只描述第一阶段。第二阶段的复杂度是O(n*log(t))。
下面分别解释一下3个if语句:
(1) j=0且s+k<=i+1
如果没有找到v=b[i]的元素,那么就要判断当前元素b[i]是否应该放入集合t中。如果b[i]不应该放入集合t中,说明本次迭代之后b[i]出现的次数上限(s+k)/k<=(i+1)k,即有条件:s+k<=i+1。
(2) j=0且s+k>i+1
如果b[i]应该放入集合t中,说明本次迭代之后b[i]出现的次数上限(s+k)/k>(i+1)k,即有条件:s+k>i+1。
(3) j!=0
如果找到v=b[i]的元素,那么就要更新c_j的值。因为v_j的出现次数增加1,所以c_j/k也增加1,即令c_j = c_j + k。
当处理完b[i]之后,可能集合t中的某些元素的c值已经过期了。过期的条件是什么呢?因为有条件:c/k >= (v的出现次数) > i/k,即 c/k>i/k,化简为c>i。i是递增的,而c有可能没有增加,所以当c=i 时,c就过期了。这时就要把这样的元素从t中删除。为什么还要更新s的值呢?我还没想明白。
算法第一阶段的复杂度也是O(n*log(t))。而t最多有多少个元素呢?论文中说|t|最大值是k*log(k)。所以算法总体复杂度是O(n*k*log(k))。
这个问题的简单形式是“查找出现次数大于n/2的重复元素”。我们先从简单问题开始,然后再做扩展。
1.查找出现次数大于n/2的重复元素
《编程之美》中有同样的一道题《寻找发帖水王》,具体思路是每次删除两个不同的元素,最后剩下的就是要求的元素。这个结论的证明如下:
已知:n,m是正整数,n表示数组的长度,m是出现次数大于n/2的元素的个数,即m>n/2。
需要求证的结论包括两个:
(1)我们用v表示出现次数大于n/2的元素。当删除两个不同元素,且其中有一个元素是v时,则m减小1,同时n要减小2。
求证:m-1>(n-2)/2
证明:m-1>n/2-1=(n-2)/2
(2)当删除两个不同元素,且其中有一个元素不是v时,则只需要n减小2。
求证:m>(n-2)/2 。这个结论是显然的。
代码如下:
int find(int array[], int n) { int candidate; int count=0; for(int i=0;i<n;++i) { if(count==0) { candidate=array[i];count=1; } else { if(candidate==array[i]) ++count; else --count; } } return candidate; }
《编程之美》的后面习题是“查找出现次数大于n/4的元素”,思路是每次删除不同的4个元素,最后剩下的3个就是候选元素,但是还要验证这3个元素是否满足条件。不再详细解释。其实《编程之美》里讲的方法就是本文后提到的“多重集”算法。
首先讲论文里的那种非多重集算法。用到的变量如下:
b[]:元素所在的数组
i:数组索引
v:保存要查找的值,就是说,当遍历过n个元素之后,v的出现次数大于n/2
c:v出现次数的上限的两倍,即v的出现次数小于等于c/2。
初始化,i=0,v=null ,c=0。
遍历b[]数组,每当遍历到b[i]时,有两种情况会发生:
(1)b[i]=v
因为b[i]=v,所以v的出现次数又增加1,为了维护c的含义,即让c/2也增加1,那么c要增加2。
(2)c=i
这一步,我们要证明b[0...i-1]中任何元素的出现次数都不超过i/2,那么必须更新v。
我们先推出前一次遍历时c和i的关系: c每次增加2,i每次增加1,为了使本次遍历时让c=i,那么在前一次遍历中,i肯定是增加了1,而c没有增加。所以在前一次遍历之前c和i的关系是c=i+1,而前一次遍历结束时c才与i 相等。
再考察前一次遍历时的情形:我们令m表示v的出现次数,那么在前一次遍历之前的情况是 m>i/2 且 m<=
c/2=(i+1)/2,则有式子:i/2<m<=(i+1)/2,注意,此时i是奇数(根据c=i+1,c是偶数),那么m只能等于(i+1)/2,即m=c/2。
而本次遍历面临的情况是c=i,在数组b[0...i-1] 中,v的出现次数是c/2,而除去v之外的其他元素的出现次数是i-m=i-c/2=i-i/2=i/2,这说明b[0...i-1]里任意一个元素的出现次数都没有超过i/2,那么我们必须更新v。最有可能的元素就是b[i],其实有可能最终b[i]也不是。
代码:
const int N=9; int b ={2,3,3,2,2,2,1,1,2}; int v=-1, c=0, i=0; while(i!=N) { if(v==b[i]) { c+=2; } else if(c==i) { v=b[i]; c+=2; } ++i; } cout<<v<<endl;
如果不存在满足条件的v,那么v的取值是最后一个数。
2.查找出现次数大于n/k的元素
给定2<=k<=n和数组b[0...n-1],我们要查找出现次数大于n/k的元素。
在问题1.中,实际上是把数组分成了两个集合,一个集合只包含一个元素v(或空),另一个集合包含出现次数小于等于n/2的元素。类似地,我们将其扩展,仍然是把数组分成两个集合,一个集合包含出现次数可能大于n/k的元素,一个集合包含出现次数小于等于n/k的元素。
相关变量声明如下:
v:出现次数可能大于n/k的元素
c:v的出现次数不超过c/2
集合t:包含的是形如(v,c)的值对。
s:对于出现次数小于等于n/k的元素,我们设其出现次数的上限为s/k。
算法包含两个阶段:首先,选出集合t;然后,验证t中的元素是否满足条件。我们在这里只描述第一阶段。第二阶段的复杂度是O(n*log(t))。
i=0,s=0,t={}; for(;i<n;++i) { 在t中找出v=b[i]的元素(v_j,c_j),j是其下标; 如果不存在这样的元素,则令j=0; if(j==0 && s+k<=i+1) { s=s+k; } else if(j==0 && s+k>i+1) { 把(b[i],s+k)加入集合t中; } else // j!=0 { c_j=c_j+k; } i=i+1; 删除t中所有满足c=i的元素。若有元素被删除,则设s=i; }
下面分别解释一下3个if语句:
(1) j=0且s+k<=i+1
如果没有找到v=b[i]的元素,那么就要判断当前元素b[i]是否应该放入集合t中。如果b[i]不应该放入集合t中,说明本次迭代之后b[i]出现的次数上限(s+k)/k<=(i+1)k,即有条件:s+k<=i+1。
(2) j=0且s+k>i+1
如果b[i]应该放入集合t中,说明本次迭代之后b[i]出现的次数上限(s+k)/k>(i+1)k,即有条件:s+k>i+1。
(3) j!=0
如果找到v=b[i]的元素,那么就要更新c_j的值。因为v_j的出现次数增加1,所以c_j/k也增加1,即令c_j = c_j + k。
当处理完b[i]之后,可能集合t中的某些元素的c值已经过期了。过期的条件是什么呢?因为有条件:c/k >= (v的出现次数) > i/k,即 c/k>i/k,化简为c>i。i是递增的,而c有可能没有增加,所以当c=i 时,c就过期了。这时就要把这样的元素从t中删除。为什么还要更新s的值呢?我还没想明白。
算法第一阶段的复杂度也是O(n*log(t))。而t最多有多少个元素呢?论文中说|t|最大值是k*log(k)。所以算法总体复杂度是O(n*k*log(k))。
相关文章推荐
- 查找出现次数大于n/k的重复元素 --- 多重集算法
- 查找出现次数大于n/k的重复元素
- 程序员面试题目总结--数组(三)【旋转数组的最小数字、旋转数组中查找指定数、两个排序数组所有元素中间值、数组中重复次数最多的数、数组中出现次数超过一半的数】
- java 找出n个元素数组中重复次数最多的数(假设出现次数大于n/2)
- 程序员面试题目总结--数组(二)【二分查找、找出给定数字出现次数、两个有序整型数组交集、找出数组中唯一的重复元素、判断数组中的数值是否连续相邻】
- 寻找水王--寻找序列中出现次数大于一半的元素
- 剑指Offer-29-查找数组中出现次数超过一般的元素
- 剑指Offer-29-java实现查找数组中出现次数超过一半的元素
- Linux查找大体积文件,及vim查找统计重复字符出现的次数
- leetcode_Majority Element (找出现次数大于一半的元素)-easy
- 寻找一个主元(出现次数大于总元素的一半)的算法
- 求一个数组重复出现次数最多的元素以及下标位置
- C#查找列表中所有重复出现元素的方法
- 解析、查找数组中重复出现的元素(Java)
- c#面试题 查找整型数组中重复出现次数最多的数字,次数相同数字按从小到大排序
- 求数组中出现次数大于N/2的元素(majority number)
- 找出数组中的一个元素出现次数大于数组长度的一半的元素,若不存在返回0
- C#查找列表中所有重复出现元素代码
- java统计List中的元素重复出现的次数和对map按key或键值排序
- 找出数组中出现次数大于n/2的元素