Boyer–Moore投票算法

背景:

​ 想象着这样一个画面:会议大厅站满了投票代表,每个都有一个牌子上面写着自己所选的候选人的名字。然后选举意见不合的(所选的候选人不同)两个人,会打一架,并且会同时击倒对方。显而易见,如果一个人拥有的选票比其它所有人加起来的选票还要多的话,这个候选人将会赢得这场“战争”,当混乱结束,最后剩下的那个代表(可能会有多个)将会来自多数人所站的阵营。但是如果所有参加候选人的选票都不是大多数(选票都未超过一半),那么最后站在那的代表(一个人)并不能代表所有的选票的大多数。因此,当某人站到最后时,需要统计他所选的候选人的选票是否超过一半(包括倒下的),来判断选票结果是否有效。

用途

找大多数。

Boyer-Moore投票算法步骤

  1. 分组阶段:两个不同选票的人进行对抗,当剩下的人都是同一阵营时,分组结束。

    简化:只需要记住候选者的名字和目前票数,每遇到一个不同候选者,目前票数-1,反之则+1,当票数归0时,更换候选者为当前候选者,并将票数置为1。

  2. 检验:对最后剩下的票数进行检验,验证的确超过一半

思想及正确性证明

​ 减治的思想,即如果原集合 S S S中有众数 m m m,那么删去两个不同元素后的集合 S ′ S' S中众数仍然为 m m m,即最终剩下的元素一定为答案(如果众数存在)。

​ 更通俗的说,众数的性质决定了它的出现次数要多于其余数字,如果最终count为负数或为0,则说明不存在众数,在这种情况下,candidate是一个随机数。

数学证明:

每次从序列里选择两个不相同的数字删除掉(或称为「抵消」),最后剩下一个数字或几个相同的数字,就是出现次数大于总 数一半的那个元素。假设我们当前数组中存在次数大于总数一半的元素为 x x x,数组的总长度为 n n n,则我们可以把数组分为两部分,一部分为相同的 k k k个元素 x x x,另一部分为 n − k 2 \frac{n-k}{2} 2nk 对个不同的元素配对,此时我们假设还存在另外一个次数大于总数一半的元素 y y y,则此时 y y y 因该满足 y > n 2 y>\frac{n}{2} y>2n ,但是按照我们之前的推理 y y y 应当满足 y ≤ n − k 2 y \le \frac{n-k}{2} y2nk ,二者自相矛盾。

简单实现

int majority(vector<int>& nums){
  int candidate,count=0;
  int n =nums.size();
  for(int i =0;i<n;i++){
    if(count==0){
      candidate=nums[i];
    	count=1;
    }
    count+=(nums[i]==candidate)?1:-1;
  }
  if(count<=0) return -1;
  int check=0;
  for(int i =0;i<n;i++){
    if(nums[i]==candidate)
      check++;
  }
  return check>(n/2)?candidate:-1;
}
复杂度

时间复杂度: O ( n ) O(n) O(n),只需遍历一次

空间复杂度: O ( 1 ) O(1) O(1),原地实现

普遍实现

​ 如果我们需要在n个数里选出m个代表,并且代表的选票数需超过 n m + 1 \cfrac{n}{m+1} m+1n张才有效,那么需要将candidate转换为数组,其余不变。

    vector<int> majority(vector<int>& nums, int m) {
    int n = nums.size();
    vector<int> ans;
    if (n == 0) return ans;
    vector<int>candidates(m, -1);
    vector<int>counts(m, 0);
    //分组
    for (int i = 0; i < n; i++) {
        bool equal = false;//检验是否与candidates里的数相等
        for (int j = 0; j < m; j++) {
            if (nums[i] == candidates[j]) {
                counts[j]++;
                equal = true;
                break;
            }
        }
        if (!equal) {
            bool zero = false;//检验是否有count为0的
            for (int j = 0; j < m; j++) {
                if (counts[j] == 0) {
                    candidates[j] = nums[i];
                    counts[j] = 1;
                    zero = true;
                    break;//有count为0的直接替换,并不进行对抗
                }
            }
            if (!zero)//没有被替换,则开始对抗
                for (int j = 0; j < m; j++) {
                    counts[j]--;
                    //我们可以很自然的写出这两行代码,但注意到,我们需要更换cnt为0的候选人
                    //那么应该在减之前进行更换,否则counts为负数了
                }
        }
    }
    //检验
    vector<int>check(m, 0);
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (counts[j] > 0 && nums[i] == candidates[j])
                check[j]++;//count>0才是有效的大多数
        }
    }
    for (int j = 0; j < m; j++) {
        if (counts[j] > 0 && check[j] > n / (m + 1))
            ans.push_back(candidates[j]);
    }
    return ans;
}
复杂度

时间复杂度: O ( n m ) O(nm) O(nm),实际上,m大多数情况是一个常数,则也是线性的

空间复杂度: O ( m ) O(m) O(m),同理,m大多数情况是一个常数,故是常数级的

练习

169. 多数元素 - 力扣(LeetCode)

229. 多数元素 II - 力扣(LeetCode)

你可能感兴趣的:(算法学习,算法)