想象着这样一个画面:会议大厅站满了投票代表,每个都有一个牌子上面写着自己所选的候选人的名字。然后选举意见不合的(所选的候选人不同)两个人,会打一架,并且会同时击倒对方。显而易见,如果一个人拥有的选票比其它所有人加起来的选票还要多的话,这个候选人将会赢得这场“战争”,当混乱结束,最后剩下的那个代表(可能会有多个)将会来自多数人所站的阵营。但是如果所有参加候选人的选票都不是大多数(选票都未超过一半),那么最后站在那的代表(一个人)并不能代表所有的选票的大多数。因此,当某人站到最后时,需要统计他所选的候选人的选票是否超过一半(包括倒下的),来判断选票结果是否有效。
找大多数。
分组阶段:两个不同选票的人进行对抗,当剩下的人都是同一阵营时,分组结束。
简化:只需要记住候选者的名字和目前票数,每遇到一个不同候选者,目前票数-1,反之则+1,当票数归0时,更换候选者为当前候选者,并将票数置为1。
检验:对最后剩下的票数进行检验,验证的确超过一半
减治的思想,即如果原集合 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} 2n−k 对个不同的元素配对,此时我们假设还存在另外一个次数大于总数一半的元素 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} y≤2n−k ,二者自相矛盾。
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)