周末了,抽点时间练习算法,顺便保持对代码的感觉,免得生疏。毕竟,拳不离手,曲不离口。
今天,我们来看阿里巴巴公司的一道面试算法题目,初看起来挺简单的,其实不然。题目如下:
已知数组中有一个数字出现的次数,超过数组长度的一半,要求找出这个数字。
有的人看到题目后就开始做,也能得到正确结果,但无法通过面试,这是为什么呢?且往下看。
一.暴力排序
排序是最容易想到的一种方法。由于目标元素的次数超过数组长度的一半,所以排序后直接取中间元素就行。
阿里巴巴会出这么简单的题目吗?有点搞笑!我们知道,基于比较的排序,时间复杂度能达到O(NlogN), 但这不是最好的方法,无法通过面试。
二.计数统计
既然题目的意思是求出这个次数超过一半的元素,那就对次数进行计数吧,直接搞个hashmap就行了。此时,时间复杂度是O(N), 但空间复杂度也是O(N),无法通过面试。
而且要注意到,使用计数法的时候,漏用了题目中的信息:超过数组长度的一半。显然,这是不合理的。这就跟你高考数学一样,你发现有个条件居然没用到,而你把题目做出来了,那是很值得怀疑的。
三.摩尔投票
接下来,我们来介绍一种很巧妙的思路,即摩尔投票法,时间复杂度是O(N), 空间复杂度是O(1),能满足面试要求。思路是怎样的呢?
我们来看一种现实中的投票场景:
班级要开始选班长
要求半数以上通过
选出来的就是班长
我们先来看一种简单的情况:假如只有2个人A和B竞争班长这一职位。那么,在大家投票后,我们从投票箱中取数后,可以这样统计:
-
先抽一张票,如果是A,则记录当前胜利者为A,票数:1
-
再抽一张票,如果是A,则记录当前胜利者为A,票数:2
-
再抽一张票,如果是B,则抵消,记录当前胜利者为A,票数:1
-
再抽一张票,如果是B,则抵消,记录无当前胜利者
-
再抽一张票,如果是A,则记录当前胜利者为A,票数:1
-
...
如此循环,直到所有票统计完毕,最后有票的一方,就是胜利者,就是大家选出来的班长。
如上只是两方进行PK, 那么,当候选人为多人时,也可以使用类似的思路,这里的前提条件就是:一定存在多余半数票的候选人。
当多方进行PK时,占据着半数以上票数的班长,可以任性地抵消任何人的票,而且其他人之间的相互抵消也不会撼动班长的位置。
有的朋友看到这里,可能觉得有点绕,那么,我们一起来看看程序,并打印关键步骤,有兴趣的朋友可以对照程序和结果理解下。
阿里巴巴主要是用Java编程, 但也有C++岗位,如下是对应的C++程序代码:
#include <iostream>
using namespace std;
int solution(int a[], int n) {
int count = 0, value = a[0];
for(int i = 0; i < n; i++)
{
printf("log, i=%d, count=%d, value=%d\n", i, count, value);
if(count == 0)
{
value = a[i];
}
if(a[i] == value)
{
count++;
}
else
{
count--;
}
}
return value;
}
int main()
{
int a[]={6,2,5,3,4,2,2,2,2,5};
int n = sizeof(a) / sizeof(a[0]);
cout << solution(a, n) << endl;
return 0;
}
结果如下(经检验无误):
log, i=0, count=0, value=6
log, i=1, count=1, value=6
log, i=2, count=0, value=6
log, i=3, count=1, value=5
log, i=4, count=0, value=5
log, i=5, count=1, value=4
log, i=6, count=0, value=4
log, i=7, count=1, value=2
log, i=8, count=2, value=2
log, i=9, count=3, value=2
2
好的,先说这么多,希望大家在刷题过程中,获得启发,举一反三,融会贯通,有所进步,拿到更多更好的offer, 薪资翻倍。
外界有点喧嚣和浮躁,然而我们可以稍微宁静一下,安心学习和积累,学习本身就是一件很纯粹快乐的事情,谁说不是呢?