题外话

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

问题描述

给定一个数组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) 大元素。代码如下, 然而这种算法在大数组的情况下回超时。

 #include <iostream>
#include <string>
#include <vector>
#include <set>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <queue>
#include <stack>
#include <algorithm>
#include <functional>
#include <utility>
#include <cstdio>
#include <cstdlib>
using namespace std; /*
* Return a position k, such that all the elements in [left, k)
* are smaller than or equal to num[k] && all the elements in
* (k, right] are larger than num[k]
*
* Note that here is the randomize version of partition, every time
* we choose a random number as the pivot.
*/
int RandomPartition(vector<int> &num, int left, int right)
{
// random partition
int rnd = rand() % (right - left + ) + left;
swap(num[rnd], num[right]); // pivot
int x = num[right];
int i = left - ;
for (int j = left; j < right; ++j)
{
if (num[j] <= x)
{
swap(num[j], num[i + ]);
i += ;
}
}
swap(num[right], num[i+]);
return i + ;
} /*
* Return the i-th ordered element in num[left, right] but without
* sorting the array.
*/ int RandomSelect(vector<int> &num, int left, int right, int i)
{
if (right - left + < i || left > right) return -; // partition the num[], return the pivot position m
int m = RandomPartition(num, left, right); // k is the number of element in num[left, m]
int k = m - left + ; if (k == i)
{
return num[m];
}
else if (k > i)
{
// find the i-th ordered element in num[left, m-1]
return RandomSelect(num, left, m - , i);
}
else
{
// find the (i-k)-th ordered element in num[m+1, right]
return RandomSelect(num, m + , right, i - k);
}
} /*
* return the median of num[]
*/
int majorityElement(vector<int> &num)
{
int n = num.size();
int mid = n / ;
return RandomSelect(num, , n-, mid);
} int main()
{
int a[] = {, , };
int b[] = {, , , }; vector<int> v1(a, a + );
vector<int> v2(b, b + ); cout << majorityElement(v1) << endl;
cout << majorityElement(v2) << endl; return ;
}

解法三

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

 #include <iostream>
#include <string>
#include <vector>
#include <set>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <queue>
#include <stack>
#include <algorithm>
#include <functional>
#include <utility>
#include <cstdio>
#include <cstdlib>
using namespace std; int majorityElement(vector<int>& num)
{
int n = num.size();
if (n < ) return -;
if (n == ) return num[]; int x = num[];
int mark = ;
for (int i = ; i < n; ++i)
{
if (mark == )
{
mark = ;
x = num[i];
}
else if (num[i] == x)
{
mark++;
}
else if (num[i] != x)
{
mark--;
}
}
return x;
} int main()
{
int a[] = {, , };
int b[] = {, , , }; vector<int> v1(a, a + );
vector<int> v2(b, b + ); cout << majorityElement(v1) << endl;
cout << majorityElement(v2) << endl; return ;
}

参考文献

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

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

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

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

    [经典算法题]寻找数组中第K大的数的方法总结 责任编辑:admin 日期:2012-11-26   字体:[大 中 小] 打印复制链接我要评论   今天看算法分析是,看到一个这样的问题,就是在一堆数据 ...

  2. Java实现 蓝桥杯 算法训练 寻找数组中最大值

    算法训练 寻找数组中最大值 时间限制:1.0s 内存限制:512.0MB 提交此题 问题描述 对于给定整数数组a[],寻找其中最大值,并返回下标. 输入格式 整数数组a[],数组元素个数小于1等于10 ...

  3. 减治算法之寻找第K小元素问题

    一.问题描写叙述 给定一个整数数列,寻找其按递增排序后的第k个位置上的元素. 二.问题分析 借助类似快排思想实现pation函数.再利用递归思想寻找k位置. 三.算法代码 public static ...

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

    PS1:如果单纯为做出这道题那么这个代价是O(nlgn),通过排序就可以了. 这里讨论的是O(n)的算法.那么来分析一下这个算法是如何做到O(n)的,算了不分析了,这个推到看起来太麻烦了.其实我想知道 ...

  5. 主元素问题 Majority Element

    2018-09-23 13:25:40 主元素问题是一个非常经典的问题,一般来说,主元素问题指的是数组中元素个数大于一半的数字,显然这个问题可以通过遍历计数解决,时间复杂度为O(n),空间复杂度为O( ...

  6. lintcode 中等题:Majority number II 主元素 II

    题目 主元素II 给定一个整型数组,找到主元素,它在数组中的出现次数严格大于数组元素个数的三分之一. 样例 给出数组[1,2,1,2,1,3,3] 返回 1 注意 数组中只有唯一的主元素 挑战 要求时 ...

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

    将数组A划分为两个数组A1和A2 ,各含有A的一半元素或一半多一个.若A中含有主元素x,则A1和A2中至少有一个数组含有主元素x,对A1和A2递归地计算有无主元素,若A只含有一个元素,则A的主元素就是 ...

  8. Leetcode算法【34在排序数组中查找元素】

    在之前ARTS打卡中,我每次都把算法.英文文档.技巧都写在一个文章里,这样对我的帮助是挺大的,但是可能给读者来说,一下子有这么多的输入,还是需要长时间的消化. 那我现在改变下方式,将每一个模块细分化, ...

  9. 寻找数组中第K频繁的元素

    问题是:给你一个数组,求解出现次数第K多的元素.当然leetcode上的要求是算法复杂度不能大于O(N*logN). 首先这个问题我先是在leetcode上看到,当时想了两种做法,做到一半都觉得不是很 ...

随机推荐

  1. electron 截图为空

    https://github.com/electron/electron/issues/2610

  2. rsa 公钥 私钥

    如果用于加密解密,那就是用公钥加密私钥解密(仅你可读但别人不可读,任何人都可写)如果用于证书验证,那就是用私钥加密公钥解密(仅你可写但别人不可写,任何人都可读) 最后,RSA的公钥.私钥是互相对应的. ...

  3. oracle中的分支与循环语句

    分支语句 if的三种写法一, if 2 < 1 then dbms_output.put_line('条件成立'); end if; 二, if 2 < 1 then dbms_outpu ...

  4. Python调shell

    os.system(cmd) 函数返回cmd的结束状态码,阻塞调用. os.popen(cmd) 函数返回cmd的标准输出,阻塞调用. (status, output) = commands.gets ...

  5. Python 中 (&,|)和(and,or)之间的区别

    &,|)和(and,or)是两组比较相似的运算符,用在“与”/ “或”上,在用法上有些许区别. (&,|)和(and,or)是用来比较两组变量的,格式基本上是: a & ba ...

  6. python的协程和异步io【select|poll|epoll】

    协程又叫做微线程,协程是一种用户态的轻量级的线程,操作系统根本就不知道协程的存在,完全由用户来控制,协程拥有自己的的寄存器的上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切换回来后, ...

  7. Course Schedule课程表12(用Topological Sorting)

    [抄题]: 现在你总共有 n 门课需要选,记为 0 到 n - 1.一些课程在修之前需要先修另外的一些课程,比如要学习课程 0 你需要先学习课程 1 ,表示为[0,1]给定n门课以及他们的先决条件,判 ...

  8. .NET格式化字符串详细说明

    DataFormatString属性:{0:Bxx}B为取值类型 C 以货币格式显示数值. D 以十进制格式显示数值. E 以科学记数法(指数)格式显示数值. F 以固定格式显示数值. G 以常规格式 ...

  9. 无法打开登录所请求的数据库 "****"。登录失败

    错误:无法打开登录所请求的数据库 "****".登录失败.用户 '****' 登录失败. sql2005连接时出现的错误 解决方法:权限不够,给登录名授权,赋予管理员角色,在登录名 ...

  10. Centos7下安装apache2.4 php5.6 pdo_oci oci8

    一.下载必须的安装源码包 http://httpd.apache.org/download.cgi#apache24 httpd-2.4.23.tar.gz http://apr.apache.org ...