找到重复的元素(Finding Repeated Elements)

leetcode Majority Element系列的解法讨论,参考文献

http://www.cs.utexas.edu/users/misra/scannedPdf.dir/FindRepeatedElements.pdf

问题描述:

在数组b[0..n-1]中找出所有出现频数多于n/k次的值。

基本思路:

遍历数组两次,第一次在数组中找到一个疑似众数的数值,第二次遍历计算该数值出现的频次,判定该值是否是众数。

算法:

基础算法:

i=0;
c=0;
do{
    if(v==b[i]){c+=2;i+=1;}
    else if(c==i){c+=2;i+=1;v=b[i];}
    else if(c!=i&&v!=b[i]) i+=1;
}while(i

该算法原理

  • 首先考虑循环中的第一个条件v==b[i]如果该条件被满足,则在b[i]中v出现的频次比b[i-1]多一次。所以我们将i增加1c增加2是为了让v的出现频次上限增加1vb[0:i-1]中最多出现c/2次)。
  • 再考虑第二个条件i==c时,i-c/2==i/2,此时没有哪个数字在b[0..i-1]中出现的次数多于i/2。所以在b[0…i]中出现次数多于i/2的只有可能是b[i]。
  • 让我们来看循环中的最后一个条件,当v!=b[i]的时候v[i]并不能拓展当前众数的频数,反而让当前众数在已经遍历的序列中的出现频数比例下降,于是我们保持c不变,i增加,直到ic相等的时候,如果仍然未出现一个b[i]与之前找到的众数相等,这个时候前面的序列中v已经不满足数量超过当前序列的长度的一半的条件了,它也已经不是众数了,所以我们以b[i]为新起点重新寻找众数。

第一个衍生算法:

我们想要将上面讨论的算法一般化。给定k和n,2<=k<=n,我们想要在数组b[0..n-1]中找到出现频次多于n/k的次的所有数值。上面的算法已经讨论了在k=2时的情况如何找出一个值,对于更加一般的情况2<=k<=n,在b中可能存在k-1个值出现的频次多于n/k次。对基础算法的一个简单的延伸如下:

首先我们对问题的解做一些约束:

v在b[0:i-1]中最多出现c/k次,

c>i,0<=i<=n,k整除c,

任何不是t中的pair的第一个component的数值在b[0:i-1]中最多出现s/k次,

0<=s<=i,k整除s。

上面提出的约束条件中关于集合t的很好满足,但是要找到其他数值的出现频次上限s/k却是一个难点。在我们之前对于k=2的情况的讨论中,我们约定其他的数值的频次上限是i-c/2,更一般的情况下,我们直接将k>=2情况下的约定拓展成作为t集合以外元素频次的上界。这种拓展一开始看起来很有道理,然而向t中添加一个新的pair(v,c)后会使得规定的上界减小太多。我们引入s是为了得到更加合理的频次上界。

//R是约束条件
R:v最多在b[0:n-1]中出现c/k次&&c>n&&k整除c&&序列b[0:n-1]中其他的元素出现次数最多n/k次
i=0, s=0, t={};
do{ 
        Let j be the index of a pair (v[j], c[j]) in t satisfying v[j]==b[i]
        if no such pair exists let j=0;
        if(j==0&& s+k<=i+1) {i+=1;s+=k;}
        else if(j==0&&s+k>i+1){i+=1;t.push_back{(b[i],s+k)};}
        else if(j!=0) {i+=1;c[j]+=k;}
        Delete all paris (v[j],c[j]) from t for which c[j]==i andif any are deleted,set s to i;
}while(i!=n)

第二个衍生算法 

第二个算法以来与一些极为简单的理论。考虑一个背包,背包中装了一些元素,元素中会有重复的数值,我们来考虑在背包中删除k个不同的元素,这个操作可以进行几次。一个k-reduced bag for bag B是从背包B衍生出来的,B一直执行上述操作知道不能再进行操作了。注意k-reduced bag 并不是唯一的,下面我们用背包{1,1,2,3,3}来举例说明。

找到重复的元素(Finding Repeated Elements)_第1张图片

假定B有N个元素,那么进行删除 k个不同元素的运算最多进行N/k次,最终集合中只会留下少于k个元素。只有出现在k-reduced bag for B中的元素才有可能在B中出现多于N/k次,而其他的元素都被删除了,不可能再出现,他们的出现次数最多是N/k次。

定理1  背包B中有N个物品,出现频次多于N/k的数值在背包B的k-reduced bag中。

将b[0:n-1]看作一个背包,我们可以利用上面的定理来提出一个算法。算法的结束条件是R:t是b的一个k-reduced bag。

在循环的过程中满足条件P: 0<=i<=n && t是b[0:i-1]的一个k-reduced bag&& d是t中的distinct的元素个数。

然后我们就可以写出下面的算法,这个算法不像之前提出的算法需要很多的解释。

i=0,d=0,t={};
do{
    if(b[i] not in t){
        t.push_back(b[i]);
        d+=1;
        if(d==k) Delete k distinct values from t and updat d;
        else if (d

在之前的算法中,我们不能确定t的大小。而在这个算法中,t最多有k个distinct元素,接下来我们会解释如何实现背包t使得上述算法的时间复杂度为O(n*log k)。

实现背包t:

上述算法中的背包t最多有n个元素和d个distinct的元素,d<=k。在t和d上进行的操作如下:

1. t={}。 执行一次

2. 在t中查找b[i]。执行n次。

3. 向t插入一个元素。最多执行n次。

4. 从t中删除k个不同的元素,并更新d。 

我们使用AVL(平衡二叉树)来实现t,定义一个树T,有d个结点。每个结点都是一个pari(v[j],c[j])。

这样进行上面对t的操作的时间复杂度就能约束在O(n*log(k))内了。

上述的算法是一个决策树算法,达到了最优效果,其他的算法都是不能比拟的。


最后介绍一个原理和上面介绍的基础算法类似的一个经典的查找众数的算法:

摩尔众数投票算法(Boyer-Moore majority vote algorithm)

用法:找出一个序列中的众数。

时间复杂度:O(n)

空间复杂度:O(1)

最简单形式的该算法找出一个序列中的一个众数,该元素在序列中出现的次数大于序列长度的一半;如果该序列中没有这样的众数,该算法不会发现没有众数,而且仍然会输出序列中的某个元素。在第二种情况下,我们可以再次遍历一遍序列,确认找到的数是否真的是众数。

算法步骤如下:

1. 初始化一个元素m和一个计数器i,令i=0;

2. 遍历序列中的元素x。

    (a) 如果i=0,令m=x,i=1

    (b) 如果m=x,令i=i+1

    (c) 否则i=i-1;

3. 返回m



你可能感兴趣的:(algorithm)