题目描述:

  一个大小为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. 转: Android微信智能心跳方案

    http://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=207243549&idx=1&sn=4ebe4beb8123f1b5 ...

  2. Quartz定时框架入门

    Quartz框架是Java开源的定时任务调度器,Quartz框架中有如下核心概念: 1. Job 任务接口,接口中只声明方法void execute(JobExecutionContext conte ...

  3. Linux学习笔记 (五)关机和重启命令

    一.关机命令 1.shutdown命令: shutdown [选项] [时间] 选项: -c:取消前一个关机命令 -h:关机 -r:重启 例:shutdown -r 05:30 &   //表 ...

  4. 【React Native开发】React Native控件之Image组件解说与美团首页顶部效果实例(10)

    ),React Native技术交流4群(458982758),欢迎各位大牛,React Native技术爱好者增加交流!同一时候博客左側欢迎微信扫描关注订阅号,移动技术干货,精彩文章技术推送! Im ...

  5. 在eclipse导入Java 的jar包的方法 JDBC

    在使用JDBC编程时需要连接数据库,导入JAR包是必须的,导入其它的jar包方法同样如此,导入的方法是 打开eclipse 1.右击要导入jar包的项目,点properties 2.左边选择java ...

  6. How to get the url of a page in OpenERP?

    How to get the url of a page in OpenERP? User is using OpenERP. I have a button on one web page. The ...

  7. mysql cmd连接 乱码

    cmd 默认编码方式是gbk,而我的mysql 设置的编码方式是utf-8的:因此在cmd 中执行 mysql> set names gbk;再查询,发现cmd 中乱码问题得到了解决.

  8. jquery ui dialog 中使用select2 导致select2的input失去焦点的解决方法

    在jqueryUI 的dialog中使用select2,select2的input search无论怎样都获取不到焦点? 解决方法: $(document).ready(function () { $ ...

  9. 更改 easyUI 的皮肤样式

    我的版本是:jquery-easyui-1.3.2.根据官方提供的皮肤样式,——在theme 里面: 只需要在引入的 页面中 link样式的地址改变即可: <link rel="sty ...

  10. Nginx通过CORS实现跨域(转)

    如果前端有nginx方向代理,跨域配置在前端反向代理nginx上 要做跨域域名限制 什么是CORS CORS是一个W3C标准,全称是跨域资源共享(Cross-origin resource shari ...