找出数列中个数大于总数一半的元素(编程之美2.3)
案例
数列3, 2, 3, 1, 3, 3, 2, 3中,3就是个数大于总数大于一半的元素。
思路一
对数列排序,再扫描一边,找出元素个数超过一半的元素。此时需要排序,同时需要记录每个元素出现个数,费时、费空间。
思路二
对于排好序的数列,假设总数为N,那么N/2位置的那个数必定为所求之数,这就不需要记录每个元素的个数。
思路三
对于数列,不用排序。对于其中的任意两个不同的元素,去除之后,原来那个个数大于总数一半的元素个数仍然是大于剩下元素的一半的。利用该特性遍历一遍数列就可以找出这个总数大于一半的那个元素。
具体的实施,不用每次去这些数中去找不同的两个数,只需记录当前候选目标值can,与此对应的为其个数num,目前访问的元素为cur
- num==0: can=cur, num=1
- num!=0 && can = cur: num++
- num!=0 && can != cur: num--
如果现在访问的元素与目标值相同,那么num++,不同num--;如果num=0,那么把候选元素改为此元素,并且赋值num=1
图示
通过简单分析可以得知
- 当总素为偶数时,Num最终至少为2
- 当总数为奇数时,Num最终至少为1
参考代码
#include<iostream> using namespace std; int Find(int a[], int N) { int count = 0; int candidate = 0; for(int i = 0; i < N; ++i) { if(count == 0) { candidate = a[i]; count = 1; } else if(candidate == a[i]) ++count; else --count; } return candidate; } int main() { int a[] = {3, 2, 3, 1, 3, 3, 2, 3}; int val = Find(a, sizeof(a) / sizeof(int)); cout << "Result:" << val << endl; }
结果
Result:3
性能
时间复杂度O(n), 空间复杂度O(1)
扩展
3个人发帖超过n/4,找粗这三个人
void Find(Type* ID, int N, Type candidate[3]) { Type ID_NULL;//定义一个不存在的ID int nTimes[3], i; nTimes[0]=nTimes[1]=nTimes[2]=0; candidate[0]=candidate[1]=candidate[2]=ID_NULL; for(i = 0; i < N; i++) { if(ID[i]==candidate[0]) { nTimes[0]++; } else if(ID[i]==candidate[1]) { nTimes[1]++; } else if(ID[i]==candidate[2]) { nTimes[2]++; } else if(nTimes[0]==0) { nTimes[0]=1; candidate[0]=ID[i]; } else if(nTimes[1]==0) { nTimes[1]=1; candidate[1]=ID[i]; } else if(nTimes[2]==0) { nTimes[2]=1; candidate[2]=ID[i]; } else { nTimes[0]--; nTimes[1]--; nTimes[2]--; } } return; }