【算法31】寻找数组的主元素(Majority Element)

题外话

最近有些网友来信问我博客怎么不更新了,是不是不刷题了,真是惭愧啊,题还是在刷的,不过刷题的频率没以前高了,看完《算法导论》后感觉网上很多讨论的题目其实在导论中都已经有非常好的算法以及数学证明,只是照搬的话好像意义也不是很大,希望找到些有代表性的题目在更新,另外希望能接着前面的《穷举递归和回溯算法终结篇》一系列如动态规划、贪心算法类的终结篇,在梳理自己知识结构的同时也能够帮助读者们更系统的学习算法思想。好了话不多说,进入正题。

问题描述

给定一个数组A[n], 定义数组的主元素 ( Majority Element) 为数组中出现次数超过 n/2 的元素。设计一个高效的算法来寻找数组的主元素。题目来源在这里

解法一

最容易想到的方法就是便利数组进行元素计数,然后返回元素个数大于 n/2 的元素,这种方法需要 O(n) 的时间复杂度 和 O(n) 空间复杂度,不算是一个好方法。

解法二

在解法一的基础上考虑消去 O(n) 的空间复杂度,如果元素出现次数超过 n/2,那么假设数组已经排序的话,那么中位数就是我们要找的数。进一步的我们除了中位数,我们不需要其他的数排好序。问题进一步转化为求数组的中位数,推广版本就是在O(n)的时间内寻找第 i 大的数,这在算法导论上有详细的论述,网上资料也很多。基本来说,就是利用快排的 partition 对数组进行划分,分为 [..., pivot,  ...] 三个部分,假设划分后 pivot 是第 m 个元素,如果 m == i, 则pivot 即为第 i 大元素;反之对如果 pivot 的位置在大于 i (m > i),则对 left 部分进行递归寻找第 i 大元素;反之对 right 部分进行递归寻找第 (i - m) 大元素。代码如下, 然而这种算法在大数组的情况下回超时。

 1 #include <iostream>
 2 #include <string>
 3 #include <vector>
 4 #include <set>
 5 #include <unordered_set>
 6 #include <map>
 7 #include <unordered_map>
 8 #include <queue>
 9 #include <stack>
10 #include <algorithm>
11 #include <functional>
12 #include <utility>
13 #include <cstdio>
14 #include <cstdlib>
15 using namespace std;
16
17 /*
18  * Return a position k, such that all the elements in [left, k)
19  * are smaller than or equal to num[k] && all the elements in
20  * (k, right] are larger than num[k]
21  *
22  * Note that here is the randomize version of partition, every time
23  * we choose a random number as the pivot.
24  */
25 int RandomPartition(vector<int> &num, int left, int right)
26 {
27     // random partition
28     int rnd = rand() % (right - left + 1) + left;
29     swap(num[rnd], num[right]);
30
31     // pivot
32     int x = num[right];
33     int i = left - 1;
34     for (int j = left; j < right; ++j)
35     {
36         if (num[j] <= x)
37         {
38             swap(num[j], num[i + 1]);
39             i += 1;
40         }
41     }
42     swap(num[right], num[i+1]);
43     return i + 1;
44 }
45
46 /*
47  * Return the i-th ordered element in num[left, right] but without
48  * sorting the array.
49  */
50
51 int RandomSelect(vector<int> &num, int left, int right, int i)
52 {
53     if (right - left + 1 < i || left > right) return -1;
54
55     // partition the num[], return the pivot position m
56     int m = RandomPartition(num, left, right);
57
58     // k is the number of element in num[left, m]
59     int k = m - left + 1;
60
61     if (k == i)
62     {
63         return num[m];
64     }
65     else if (k > i)
66     {
67         // find the i-th ordered element in num[left, m-1]
68         return RandomSelect(num, left, m - 1, i);
69     }
70     else
71     {
72         // find the (i-k)-th ordered element in num[m+1, right]
73         return RandomSelect(num, m + 1, right, i - k);
74     }
75 }
76
77 /*
78  * return the median of num[]
79  */
80 int majorityElement(vector<int> &num)
81 {
82     int n = num.size();
83     int mid = n / 2;
84     return RandomSelect(num, 0, n-1, mid);
85 }
86
87 int main()
88 {
89     int a[] = {3, 2, 2};
90     int b[] = {3, 2, 2, 2};
91
92     vector<int> v1(a, a + 3);
93     vector<int> v2(b, b + 4);
94
95     cout << majorityElement(v1) << endl;
96     cout << majorityElement(v2) << endl;
97
98     return 0;
99 }

解法三

这种方法的思想是把 majority element 看成是 1,而把其他的元素看成是 -1。算法首先取第一个元素 x 作为 majority element,并计 mark = 1;而后遍历所有的元素,如果元素和 x 相等, 则 mark ++;否则如果不等, 则 mark--, 如果 mark == 0, 则重置 mark = 1, 并且更新 x 为当前元素。 由于majority element 的数量大于一半,所以最后剩下的必然是majority element.  AC code 如下.

 1 #include <iostream>
 2 #include <string>
 3 #include <vector>
 4 #include <set>
 5 #include <unordered_set>
 6 #include <map>
 7 #include <unordered_map>
 8 #include <queue>
 9 #include <stack>
10 #include <algorithm>
11 #include <functional>
12 #include <utility>
13 #include <cstdio>
14 #include <cstdlib>
15 using namespace std;
16
17 int majorityElement(vector<int>& num)
18 {
19     int n = num.size();
20     if (n < 0) return -1;
21     if (n == 1) return num[0];
22
23     int x = num[0];
24     int mark = 1;
25     for (int i = 1; i < n; ++i)
26     {
27         if (mark == 0)
28         {
29             mark = 1;
30             x = num[i];
31         }
32         else if (num[i] == x)
33         {
34             mark++;
35         }
36         else if (num[i] != x)
37         {
38             mark--;
39         }
40     }
41     return x;
42 }
43
44 int main()
45 {
46     int a[] = {3, 2, 2};
47     int b[] = {3, 2, 2, 2};
48
49     vector<int> v1(a, a + 3);
50     vector<int> v2(b, b + 4);
51
52     cout << majorityElement(v1) << endl;
53     cout << majorityElement(v2) << endl;
54
55     return 0;
56 }

参考文献

[1] 《算法导论》第二版,第九章 《中位数和顺序统计学》.

[2]  http://people.cis.ksu.edu/~subbu/Papers/Majority%20Element.pdf

时间: 2024-12-14 18:09:10

【算法31】寻找数组的主元素(Majority Element)的相关文章

[经典算法题]寻找数组中第K大的数的方法总结

[经典算法题]寻找数组中第K大的数的方法总结 责任编辑:admin 日期:2012-11-26 字体:[大 中 小] 打印复制链接我要评论 今天看算法分析是,看到一个这样的问题,就是在一堆数据中查找到第k个大的值. 名称是:设计一组N个数,确定其中第k个最大值,这是一个选择问题,当然,解决这个问题的方法很多,本人在网上搜索了一番,查找到以下的方式,决定很好,推荐给大家. 所谓"第(前)k大数问题"指的是在长度为n(n>=k)的乱序数组中S找出从大到小顺序的第(前)k个数的问题.

数组的主元素查询

描述 已知一个整数序列A=(a0, a1,-an-1),其中0≤ai<n(0≤i<n).若存在ap1=ap2-=apm=x 且m>n/2(0≤pk<n,1≤k≤m),则称x为A的主元素.例如A=(0,5,5,3,5,7,5,5),则5为主元素:又如A=(0,5,5,3,5,1,5,7),则A中没有主元素.假设A中的n个元素保存在一个一维数组中,请设计一个尽可能高效的算法,找出A的主元素.若存在主元素,则输出该元素:否则输出-1. 输入 多组数据,每组数据两行.第一行为一个整数n,代

算法:寻找数组的第二大的元素(不排序、只循环一次)

该算法的原理是,在遍历数组的时,始终记录当前最大的元素和第二大的元素.示例代码: package demo01 import ( "fmt" ) func NumberTestBase() { fmt.Println("This is NumberTestBase") nums := []int{12, 24, 2, 5, 13, 8, 7} fmt.Println("nums:", nums) secondMax := getSecondMax

算法导论 寻找第i小元素 9.2

代码: 1 #include<iostream> 2 #include<ctime> 3 4 using namespace std; 5 6 int size = 10; 7 8 void Swap(int &a, int &b) 9 { 10 int c = a; 11 a = b; 12 b = c; 13 } 14 15 void RandomizedSwap(int a[], int p, int r) 16 { 17 srand((int)time(0)

求数组主元素的递归算法

数组A是具有n个元素的数组,x是A中的一个元素,若A中有一半以上的元素与A相同,则称x是数组A的主元素.例如 ,数组A={1,3,2,3,3,4,3},元素3就是该数组的主元素. 1.移去数组中的两个不同元素后,如果原来数组中有主元素,那么该主元素依然是新数组的主元素. 2.如果数组2k个元素中有k个元素相同(k<n/2),移去这2k个元素以后,如果原来数组中有主元素,那么该主元素依然是新数组的主元素. 如果新数组只剩下一个元素,该元素可作为主元素的候选者.新数组是若干个相同元素,该元素可作为主

主元素 算法

问题描述: 设T[0:n-1]是n个元素的数组.对任一元素x,设S(x)={i|T[i]=x}.当|S(x)|>n/2时,称x为T的主元素.设计一个线性时间算法,确定T[0:n-1]是否有一个主元素. 分析与解答: (1)基于分治法的线性期望时间求主元素算法 中位数:数列排序后位于最中间的那个数,如果一个数列有主元素,那么必然是中位数.求一个数列有没有主元素,只要看中位数是不是主元素. 找中位数的方法:选择一个元素作为划分起点,然后用快速排序的方法将小于它的移动到左边,大于它的移动到右边.这样将

Ex 2_23 如果一个数组超过半数的元素都相同时,该数组被称为含有一个主元素..._第二次作业

将数组A划分为两个数组A1和A2 ,各含有A的一半元素或一半多一个.若A中含有主元素x,则A1和A2中至少有一个数组含有主元素x,对A1和A2递归地计算有无主元素,若A只含有一个元素,则A的主元素就是这个元素,否则计算出A1和A2的主元素x1和x2: 若x1和x2都不存在,则A不存在主元素 若x1和x2有一个存在,则检查这个元素是否为A的主元素 若x1和x2都存在且不相等,则分别检查这个元素是否为A的主元素 若x1和x2都存在且相等,则这个元素就是A的,主元素 1 package org.xiu

[ALGO-49] 寻找数组中最大值

算法训练 寻找数组中最大值 时间限制:1.0s   内存限制:512.0MB 问题描述 对于给定整数数组a[],寻找其中最大值,并返回下标. 输入格式 整数数组a[],数组元素个数小于1等于100.输出数据分作两行:第一行只有一个数,表示数组元素个数:第二行为数组的各个元素. 输出格式 输出最大值,及其下标 样例输入 3 3 2 1 样例输出 3 0 说明:蓝桥杯官网上的"样例输入"格式不对,我在这里改成了对的格式 import java.util.Scanner; public cl

九章算法面试题37 主元素

九章算法官网-原文网址 http://www.jiuzhang.com/problem/37/ 题目 主元素(Majority Number)定义为数组中出现次数严格超过一半的数.找到这个数.要求使用O(1)的额外空间和O(n)的时间. 进阶1:如果数组中存在且只存在一个出现次数严格超过1/3的数,找到这个数.要求使用O(1)的额外空间和O(n)的时间. 进阶2:如果数组中存在且只存在一个出现次数严格超过1/k的数,找到这个数.要求使用O(k)的额外空间和O(n)的时间 解答 采用抵消法.一旦发