题目描述:

  一个大小为n的数组,里面的数都属于范围[0,n-1],有不确定的重复元素,找到至少一个重复元素,要求O(1)空间和O(n)时间

算法分析: 

  这个题目要求用O(n)的时间复杂度,这意味着只能遍历数组一次。同时还要寻找重复元素,很容易想到建立哈希表来完成,遍历数组时将每个元素映射到哈希表中,如果哈希表中已经存在这个元素则说明这就是个重复元素。因此直接使用C++ STL中的hash_set,可以方便的在O(n)时间内完成对重复元素的查找。

但是题目却在空间复杂度上有限制——要求为O(1)的空间。因此采用哈希表这种解法肯定在空间复杂度上是不符合要求的。但可以沿着哈希法的思路继续思考,题目中数组中所以数字都在范围[0, n-1],因此哈希表的大小为n即可。因此我们实际要做的就是对n个范围为0到n-1的数进行哈希,而哈希表的大小刚好为n。对排序算法比较熟悉的同学不难发现这与一种经典的排序算法——基数排序非常类似。而基数排序的时间空间复杂度刚好符合题目要求!因此尝试使用基数排序来解这道面试题。

举例分析:

  下面以2,4,1,5,7,6,1,9,0,2这十个数为例,展示下如何用基数排序来查找重复元素。

下标

0

1

2

3

4

5

6

7

8

9

数据

2

4

1

5

7

6

1

9

0

2

(1)由于第0个元素a[0] 等于2不为0,故交换a[0]与a[a[0]]即交换a[0]与a[2]得:

下标

0

1

2

3

4

5

6

7

8

9

数据

4

5

7

6

1

9

0

2

(2)由于第0个元素a[0] 等于1不为0,故交换a[0]与a[a[0]]即交换a[0]与a[1]得:

下标

0

1

2

3

4

5

6

7

8

9

数据

  1

2

5

7

6

1

9

0

2

(3)由于第0个元素a[0] 等于4不为0,故交换a[0]与a[a[0]]即交换a[0]与a[4]得:

下标

0

1

2

3

4

5

6

7

8

9

数据

  7

1

2

5

 4

6

1

9

0

2

(4)由于第0个元素a[0] 等于7不为0,故交换a[0]与a[a[0]]即交换a[0]与a[7]得:

下标

0

1

2

3

4

5

6

7

8

9

数据

1

2

5

4

6

1

0

2

(5)由于第0个元素a[0] 等于9不为0,故交换a[0]与a[a[0]]即交换a[0]与a[9]得:

下标

0

1

2

3

4

5

6

7

8

9

数据

  2

1

2

5

4

6

1

7

0

  9

(6)由于第0个元素a[0] 等于2不为0,故交换a[0]与a[a[0]]即交换a[0]与a[2],但a[2]也为2与a[0]相等,因此我们就找到了一个重复的元素——2。

下标

0

1

2

3

4

5

6

7

8

9

数据

1

5

4

6

1

7

0

9

通过上面的具体分析,得到如下代码:

 //GOOGLE面试题
//一个大小为n的数组,里面的数都属于范围[0, n-1],有不确定的重复元素,找到至少一个重复元素,要求O(1)空间和O(n)时间。
#include <stdio.h>
const int NO_REPEAT_FLAG = -;
void Swap(int &x, int &y)
{
int t = x;
x = y;
y = t;
}
//类似于基数排序,找出数组中第一个重复元素。
int RadixSort(int a[], int n)
{
int i;
for (i = ; i < n; i++)
{
while (i != a[i])
{
if (a[i] == a[a[i]])
return a[i];
Swap(a[i], a[a[i]]);
}
}
return NO_REPEAT_FLAG;
}
void PrintfArray(int a[], int n)
{
for (int i = ; i < n; i++)
printf("%d ", a[i]);
putchar('\n');
}
int main()
{
const int MAXN = ;
int a[MAXN] = {, , , , , , , , , };
printf("数组为: \n");
PrintfArray(a, MAXN);
int nRepeatNumber = RadixSort(a, MAXN);
if (nRepeatNumber != NO_REPEAT_FLAG)
printf("该数组有重复元素,此元素为%d\n", nRepeatNumber);
else
printf("该数组没有重复元素\n");
return ;
}

  整个程序的核心代码只有Radixsort()函数中短短5行左右,虽然有二重循环语句,但每个元素只会被访问一次,完成符合题目对时间复杂度的要求。

存在的问题:

  改动了原数组的数据,那么有没有一种方法既能在满足题目时间、空间复杂度要求的前提下找到数组中的重复元素,又能不改变数组中的数据呢?

 我们可以通过下面的方式实现:

 int Repeat(int *a, int n)
{
for(int i = ; i < n; i++)
{
if(a[i] > ) //判断条件
{
if(a[ a[i] ] < )
{
return a[i];//已经被标上负值了,有重复
}
else
{
a[ a[i] ]= -a[a[i]]; //记为负
} }
else // 此时|a[i]|代表的值已经出现过一次了
{
if(a[-a[i]] < )
{
return -a[i];//有重复找到
}
else
{
a[ -a[i] ] = -a[ -a[i] ];
}
}
}
return -;//数组中没有重复的数
}

下面我们通过一个具体的实例分析一下:——以取负为访问标志的方法  

  设int a[] = {1, 2, 1}

    第一步:由于a[0]等于1大于0,因此先判断下a[a[0]]即a[1]是否小于0,如果小于,说明这是第二次访问下标为1的元素,表明我们已经找到了重复元素。不是则将a[a[0]]取负,a[1]=-a[1]=-2。

    第二步:由于a[1]等于-2,因此先判断下a[-a[1]]取出a[2]是否小于0,如果小于,说明这是第二次访问下标为2的元素,表明我们已经找到了重复元素。不是则将a[-a[1]]取负,a[2]=-a[2]=-1。

    第三步:由于a[2]等于-1,因此判断下a[-a[2]]即a[1]是否小于0,由于a[1]在第一步中被取反过了,因此证明这是第二次访问下标为1的元素,直接返回-a[2]即可。

但是问题又一次的出现了:

  当数组第0个元素为0且数据中只有0重复时是无法找出正确解的。只要用:

    const int MAXN = 5;

     int a[MAXN] = {0, 1, 2, 3, 0};

  这组数据来测试,就会发现该方法无法判断0是个重复出现的元素。

解决方法:

  这个算法之所以用到了取负,是因此根据题目条件,数组中数据范围为[0,n-1],因此可以通过判断元素是否大于0来决定这个元素是未访问过的数据还是已访问过的数据。但也正因为对0的取负是无效操作决定了这个算法存在着缺陷。要改进一下也很简单——不用取负,而用加n。这样通过判断元素是否大于等于n就能决定这个元素是未访问过的数据还是已访问过的数据

 改进代码如下:

 //GOOGLE面试题
//一个大小为n的数组,里面的数都属于范围[0, n-1],有不确定的重复元素,找到至少一个重复元素,要求O(1)空间和O(n)时间。
#include <stdio.h>
const int NO_REPEAT_FLAG = -;
int FindRepeatNumberInArray(int *a, int n)
{
for(int i = ; i < n; i++)
{
int nRealIndex = a[i] >= n ? a[i] - n : a[i];
if (a[nRealIndex] >= n) //这个位置上的值大于n说明已经是第二次访问这个位置了
return nRealIndex;
else
a[nRealIndex] += n;
}
return NO_REPEAT_FLAG; //数组中没有重复的数
}
void PrintfArray(int a[], int n)
{
for (int i = ; i < n; i++)
printf("%d ", a[i]);
putchar('\n');
}
int main()
{
const int MAXN = ;
int a[MAXN] = {, , , , , , , , , };
printf("数组为: \n");
PrintfArray(a, MAXN);
int nRepeatNumber = FindRepeatNumberInArray(a, MAXN);
if (nRepeatNumber != NO_REPEAT_FLAG)
printf("该数组有重复元素,此元素为%d\n", nRepeatNumber);
else
printf("该数组没有重复元素\n");
return ;
}

一道Google面试题——基数排序思想的更多相关文章

  1. 一道google面试题

    输入n,把1-n分成两个和相等的子集,有多少种分法 想了个dp,直接背包也行 #include <iostream> #include <cstdio> using names ...

  2. 一道google面试题(dp)

    输入n,把1-n分成两个和相等的子集,有多少种分法 想了个dp,直接背包也行 #include <iostream> #include <cstdio> using names ...

  3. Google面试题之100层仍两个棋子

    一道Google面试题,题目如下:"有一个100层高的大厦,你手中有两个相同的玻璃围棋子.从这个大厦的某一层扔下围棋子就会碎,用你手中的这两个玻璃围棋子,找出一个最优的策略,来得知那个临界层 ...

  4. 一道sql面试题(查询语句)

    一道sql面试题(查询语句)   id name age 1  a        11 2  b        11 3  c        12 4  d        13 5  e        ...

  5. 【google面试题】求1到n的正数中1出现的次数的两种思路及其复杂度分析

    问题描写叙述: 输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数.比如输入12,从1到12这些整数中包括1 的数字有1.10.11和12.1一共出现了5次. 这是一道广为流传的googl ...

  6. 一道Python面试题

    无意间,看到这么一道Python面试题:以下代码将输出什么? def testFun():    temp = [lambda x : i*x for i in range(4)]    return ...

  7. 数组中第K小的数字(Google面试题)

    http://ac.jobdu.com/problem.php?pid=1534 题目1534:数组中第K小的数字 时间限制:2 秒 内存限制:128 兆 特殊判题:否 提交:1120 解决:208 ...

  8. Google 面试题和详解

    Google的面试题在刁钻古怪方面相当出名,甚至已经有些被神化的味道.这个话题已经探讨过很多次,而科技博客 BusinessInsider这两天先是贴出15道Google面试题并一一给出了答案,其中不 ...

  9. Google 面试题:Java实现用最大堆和最小堆查找中位数 Find median with min heap and max heap in Java

    Google面试题 股市上一个股票的价格从开市开始是不停的变化的,需要开发一个系统,给定一个股票,它能实时显示从开市到当前时间的这个股票的价格的中位数(中值). SOLUTION 1: 1.维持两个h ...

随机推荐

  1. 倍福TwinCAT(贝福Beckhoff)常见问题(FAQ)-如何配置虚拟轴 TC3

    在Motion上添加一个NC Task   在Axis上右击添加一个轴,类型为Continuous Axis   在PLC上右击添加新项,然后添加一个PLC项目   在引用中添加TC2_MC2的库引用 ...

  2. LogManager

    public class LogManager { // Fields public static bool Debugstate; // Methods public static void Log ...

  3. redis源代码分析(5)——aof

    前面几篇基本介绍了redis的主要功能.流程.接下来是一些相对独立的部分,首先看一下持久化. redis持久化支持两种方式:RDB和AOF,我们首先看一下AOF的实现. AOF(Append only ...

  4. 在php中如何用 union all统计总条数?

    网上使用union all 查询记录总条数的参考资料比较少,所以记录下来,以便有同样需求的人使用. $rs_num = Db::query("select sum(a.b) as num f ...

  5. 简体字冯|docker-安装docker私有库

    原创文章,转载请注明出处. 作者:简体字丶冯; QQ:564372931 安装docker 各终端安装docker 教程 菜鸟docker教程 就挺好,本着不重复造轮子的原则就不深入了,自己学习. 如 ...

  6. 解决:System.Data.SqlClient.SqlError: FILESTREAM 功能被禁用

    还原 AdventureWorks Sample DataBase 时遇到 FILESTREAM feature is disabled 错误提示. FileStream是SQL Server 200 ...

  7. Mysql查询缓存碎片、缓存命中率及Nagios监控

    Mysql 的优化方案,在互联网上可以查找到非常多资料,今天对Mysql缓存碎片和命中率作了详细了解,个人作了简单整理. 一.Mysql查询缓存碎片和缓存命中率. mysql> SHOW STA ...

  8. 基于RocketIO的高速串行协议设计与实现

    随着对信息流量需求的不断增长, 传统并行接口技术成为进一步提高数据传输速率的瓶颈.过去主要用于光纤通信的串行通信技术—SERDES正在取代传统并行总线而成为高速接口技术的主流.SERDES 是串行器) ...

  9. python学习之sys.getsizeof()

    sys.getsizeof() >>> help(sys.getsizeof)Help on built-in function getsizeof in module sys: g ...

  10. java中过滤四字节字符

    private static final String FOUR_BYTE_FILTER = "[\\ud800\\udc00-\\udbff\\udfff\\ud800-\\udfff]& ...