一道有趣的GOOGLE面试题——找出至少一个重复元素

2023-10-28

一道有趣的GOOGLE面试题——找出至少一个重复元素
题目:
一个大小为n的数组,里面的数都属于范围[0, n-1],有不确定的重复元素,找到至少一个重复元素,要求O(1)空间和O(n)时间。
   这个题目要求用O(n)的时间复杂度,这意味着只能遍历数组一次。同时还要寻找重复元素,很容易想到建立哈希表来完成,遍历数组时将每个元素映射到哈希表中,如果哈希表中已经存在这个元素则说明这就是个重复元素。因此直接使用C++ STL中的hash_set可以方便的在O(n)时间内完成对重复元素的查找。

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

 

    下面以2415761902这十个数为例,展示下如何用基数排序来查找重复元素。

下标

  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

数据

  1

  4

  2

  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

数据

  4

  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

数据

  9

  1

  2

  5

  4

  6

  1

  7

  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]也为2a[0]相等,因此我们就找到了一个重复的元素——2

下标

  0

  1

  2

  3

  4

  5

  6

  7

  8

  9

数据

  2

  1

  2

  5

  4

  6

  1

  7

  0

  9

 根据分析代码如下:

//GOOGLE面试题
//一个大小为n的数组,里面的数都属于范围[0, n-1],有不确定的重复元素,找到至少一个重复元素,要求O(1)空间和O(n)时间。
//By MoreWindows (http://blog.csdn.net/MoreWindows)
#include <stdio.h>
const int NO_REPEAT_FLAG = -1;
void Swap(int &x, int &y)
{
	int t = x;
	x = y;
	y = t;
}
//类似于基数排序,找出数组中第一个重复元素。
int RadixSort(int a[], int n)
{
	int i;
	for (i = 0; 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 = 0; i < n; i++)
		printf("%d ", a[i]);
	putchar('\n');
}
int main()
{
	printf("    白话经典算法系列之十 一道有趣的GOOGLE面试题 \n");      
	printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n"); 

	const int MAXN = 10;
	int a[MAXN] = {2, 4, 1, 5, 7,  6, 1, 9, 0, 2};
	//int a[MAXN] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

	printf("数组为: \n");
	PrintfArray(a, MAXN);

	int nRepeatNumber = RadixSort(a, MAXN);
	if (nRepeatNumber != NO_REPEAT_FLAG)
		printf("该数组有重复元素,此元素为%d\n", nRepeatNumber);
	else
		printf("该数组没有重复元素\n");
	return 0;
}

    运行结果如下图所示:

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


    用基数排序来解决这道题目虽然思维巧妙,但会改动原数组的数据。有没有一种解法既找出一个重复的数据,又不改动数组中的数据了?请看下面的解法。

int Repeat(int *a, int n)
{
	for(int i = 0; i < n; i++)
	{
		if(a[i] > 0) //判断条件
		{
			if(a[ a[i] ] < 0)
			{
				return a[i];//已经被标上负值了,有重复
			}
			else 
			{
				a[ a[i] ]= -a[a[i]]; //记为负
			}

		}
		else // 此时|a[i]|代表的值已经出现过一次了
		{
			if(a[-a[i]] < 0)
			{
				return -a[i];//有重复找到
			}
			else 
			{
				a[ -a[i] ] = -a[ -a[i] ];
			}
		}
	}
	return -1;//数组中没有重复的数
}

下面对这种以取负为访问标志的方法用个实例来说明下:

    设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]即可。

 

这种通过取负来判断元素是否重复访问的方法正如网友jwfeng002所言,当数组第0个元素为0且数据中只有0重复时是无法找出正确解的。只要用:

       const int MAXN = 5;

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

这组数据来测试,就会发现该方法无法判断0是个重复出现的元素。运行结果如下图所示:

 

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

//GOOGLE面试题
//一个大小为n的数组,里面的数都属于范围[0, n-1],有不确定的重复元素,找到至少一个重复元素,要求O(1)空间和O(n)时间。
//By MoreWindows (http://blog.csdn.net/MoreWindows)
#include <stdio.h>
const int NO_REPEAT_FLAG = -1;
int FindRepeatNumberInArray(int *a, int n)
{
	for(int i = 0; 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 = 0; i < n; i++)
		printf("%d ", a[i]);
	putchar('\n');
}
int main()
{
	printf("    白话经典算法系列之十一 一道有趣的GOOGLE面试题解法2\n");      
	printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n"); 

	const int MAXN = 10;
	//int a[MAXN] = {2, 4, 1, 5, 7,  6, 1, 9, 0, 2};
	int a[MAXN] = {0, 1, 2, 3, 4,  5, 6, 7, 8, 0};
	
	printf("数组为: \n");
	PrintfArray(a, MAXN);

	int nRepeatNumber = FindRepeatNumberInArray(a, MAXN);
	if (nRepeatNumber != NO_REPEAT_FLAG)
		printf("该数组有重复元素,此元素为%d\n", nRepeatNumber);
	else
		printf("该数组没有重复元素\n");
	return 0;
}

运行结果如图所示:

转载于:  http://blog.csdn.net/morewindows/article/details/8212446
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一道有趣的GOOGLE面试题——找出至少一个重复元素 的相关文章

  • 字节跳动第五届青训营后端练习题——分割ip(Java版)

    题目 有效 IP 地址正好由四个整数 每个整数位于 0 到 255 之间组成 且不能含有前导 0 整数之间用 分隔 例如 0 1 2 201 和 192 168 1 1 是有效 IP 地址 但是 0 011 255 245 192 168
  • # 洗牌算法

    基本概念 等概率将将一个数组N打乱 概率每次都是1 N 加上 方法一 全局洗牌 从 0到N 1的数组下标 每次随机产生两个0到 N 1之间的数 进行交换 void get rand number int array int length i
  • 数学,金融,计算机优秀博客

    数学 金融 计算机优秀博客 网址 http zhiqiang org blog link 阅微堂 阅微堂认同的优秀博客标准为 有更新 原创 一年以前的文章还有价值 数学 谢松 木遥 Han Yan 统计之都 张驰原 李淼 科学松鼠会 卢昌海
  • Nvidia-docker运行错误- Nvidia-docker : Unknown runtime specified nvidia

    使用nvidia docker的时候 出现了上面的bug 故总结一下 所使用的环境为ubuntu系统64位 GPU2080ti command1 sudo nvidia docker run e NVIDIA VISIBLE DEVICES
  • 几种常用激活函数的简介

    1 sigmod函数 函数公式和图表如下图 在sigmod函数中我们可以看到 其输出是在 0 1 这个开区间内 这点很有意思 可以联想到概率 但是严格意义上讲 不要当成概率 sigmod函数曾经是比较流行的 它可以想象成一个神经元的放电率
  • 一道创新工场面试题详解:共打了多少鱼?

    一道创新工场面试题详解 共打了多少鱼 题目 abcde五人打渔 打完睡觉 a先醒来 扔掉1条鱼 把剩下的均分成5分 拿一份走了 b再醒来 也扔掉1条 把剩下的均分成5份 拿一份走了 然后cde都按上面的方法取鱼 问他们一共打了多少条鱼 解法
  • 关系数据库的特点

    关系数据库的特点 数据库管理系统将具有一定结构的数据组成一个集合 它主要具有以下几个特点 1 数据的结构化 数据库中的数据并不是杂乱无章 毫不相干的 它们具有一定的组织结构 属于同一集合的数据具有相似的特征 2 数据的共享性 在一个单位的各
  • 迁移学习概述

    1 迁移学习的背景 在有监督的机器学习和尤其是深度学习的场景应用中 需要大量的标注数据 标注数据是一项枯燥无味且花费巨大的任务 关键是现实场景中 往往无法标注足够的数据 而且模型的训练是极其耗时的 因此迁移学习营运而生 传统机器学习 主要指
  • linux上的一些系统监测工具简介

    linux上的一些系统监测工具简介 在linux中提供了很多有用的工具 以方便开发人员调试和评测服务器程序 下面介绍几个常用的工具 tcpdump nc strace lfos netstat vmstat ifstat和mpstat 1
  • 机器学习(machine learning)之AdaBoost算法

    转载自 http blog csdn net haidao2009 article details 7514787 菜鸟最近开始学习machine learning 发现adaboost 挺有趣 就把自己的一些思考写下来 主要参考了http
  • 枚举子集复杂度 O(n^3) 证明

    困扰多年的问题 居然在学习离散数学后的一分钟内得到解决 形式化问题为 求满足 A B S A sube B sube S A B S 的有序对
  • 中文分词之HMM模型详解

    关于HMM模型的介绍 网上的资料已经烂大街 但是大部分都是在背书背公式 本文在此针对HMM模型在中文分词中的应用 讲讲实现原理 尽可能的撇开公式 撇开推导 结合实际开源代码作为例子 争取做到雅俗共赏 童叟无欺 没有公式 就没有伤害 模型介绍
  • 贪心算法——排队打水问题

    6 3 排队打水问题 有n个人排队到r个水龙头去打水 他们装满水桶的时间为t1 t2 tn为正整数且个不相等 应如何安排他们打水顺序才能使他们花费的时间最少 算法分析 时间总和 等待时间 装水时间 采用贪心思想 先sort 默认将装水时间从
  • 秒杀linux下系统调用fork()面试题

    秒杀linux下系统调用fork 面试题 第一道题 在之前博客也写过这道题 http blog csdn net chdhust article details 8535915 题目 请问下面的程序一共输出多少个 1 2 3 4 5 6
  • 快速排序之“采取“尾递归”和“三数取中”技术的快速排序”

    快速排序之 采取 尾递归 和 三数取中 技术的快速排序 下面针对快速排序进行一些优化 QUICKSORT算法包含两个对其自身的递归调用 即调用PARTITION后 左边的子数组和右边的子数组分别被递归排序 QUICKSORT中的第二次递归调
  • 二分查找及二分答案

    一 二分思想 二分是一种常用且非常精妙的算法 常常是我们解答问题的突破口 二分的基本用途是在单调序列或单调函数中做查找操作 因此当问题的答案具有单调性时 就可以通过二分把求解转化为判定 根据复杂度理论 可知判定的难度小于求解 这使得二分的应
  • 编写C++中的两个类 一个只能在栈中分配空间 一个只能在堆中分配(腾讯2012面试题)

    编写C 中的两个类 一个只能在栈中分配空间 一个只能在堆中分配 腾讯2012面试题 这道题挺好的 HeapOnly cpp include
  • 程序员面试题精选100题(30)-赋值运算符重载函数[C/C++/C#]

    程序员面试题精选100题 30 赋值运算符重载函数 C C C 问题 给出如下CMyString的声明 要求为该类型添加赋值运算符函数 class CMyString public CMyString char pData NULL CMy
  • CRC-模2除法

    在循环冗余校验码 CRC 的计算中有应用到模2除法 模2除法的特点就是 每一位除的结果不影响其它位 即不向上一位借位 模2除法原则 1 被除数的首位为1 商为1 2 被除数的首位为0 商为0 3 模2除法等同于按位异或 要保证每次除完首位都
  • 前缀和与差分(分析与模板)

    前缀和 处理数组公式 s i s i 1 num i 输出区间和公式 s r s l 1 模板 include

随机推荐

  • linux原始套接字-发送ARP报文

    linux原始套接字 可以直接发送和接收链路层和网络层的报文 对我们理解TCP IP协议栈有很多帮助 也可写出很多有趣的程序 下面的例子是向192 168 1 60的电脑 发送伪造的ARP报文 使其更新ARP表 导致无法PING通192 1
  • 装饰器原理及应用场景

    原理 1 装饰器的实现是由闭包支撑的 2 装饰器本质上是 个python函数 它可以在让其他函数在不需 要做任何代码的变动的前提下增加额外的功能 3 装饰器的返回值也是 个函数的对象 应用场景 1 可以在外层函数加上时间计算函数 计算函数运
  • Ubuntu常用服务器环境搭建——MySQL篇

    MySQL 1 安装MySQL apt get update apt get install mysql server 2 配置MySQL vi etc mysql my cnf 也可能是 etc mysql mysql conf d my
  • IPsec中IKE与ISAKMP过程分析(主模式-消息5和消息6)

    IPsec中IKE与ISAKMP过程分析 主模式 消息1 搞搞搞高傲的博客 CSDN博客 IPsec中IKE与ISAKMP过程分析 主模式 消息2 搞搞搞高傲的博客 CSDN博客 IPsec中IKE与ISAKMP过程分析 主模式 消息3 搞
  • 与自定义词典 分词_文本挖掘

    基于文本分析的场景有词云图 基于距离的文本聚类 基于监督的文本分类 情感分析等等 不管是文档库 文章 段落或句子 针对文本挖掘的基础都集中于词的分析 即针对文档库 段落 句子等的分词 切词 词是很多中文自然语言处理的基础 分词有助于提取文档
  • SpringCloud OpenFeign模块报错问题

    SpringCloud OpenFeign模块报错问题 问题 问题原因 使用Spring Initializr初始化项目引入了openfeign 没有在意版本 直到运行项目进行远程调用时报错 由于Spring Cloud Feign在Hox
  • JDBC URL

    1 JDBC URL的概念 JDBC URL提供了一种标识数据库的方法 可以使相应的驱动程序能识别该数据库并与之建立连接 实际上 驱动程序编程员将决定用什么JDBC URL来标识特定的驱动程序 用户不必关心如何来形成JDBC URL 它们只
  • 常见的响应式布局解决方法

    由于设备的分辨率不同 我们就用响应式布局来解决设备分辨率不同的问题 常见的解决方法有px视口 媒体查询 百分比 rem 和vw vh等方法来实现响应式布局 接下来介绍下个种方法 一 px和视口 在静态网页中 我们经常用像素 px 作为单位
  • YOLOv7环境搭建、训练流程以及转TensorRT部署问题

    一 背景 github官网yolov7 代码什么的从这个网站下 还有一个 但是这是官网 二 环境搭建 有两种环境搭建方式 一是用conda搭个虚拟环境 然后安装所有需要的库跟依赖等 二是用docker容器 下载英伟达的pytorch ima
  • Flume基础知识(个人总结)

    声明 1 本文为我的个人复习总结 并非那种从零基础开始普及知识 内容详细全面 言辞官方的文章 2 由于是个人总结 所以用最精简的话语来写文章 3 若有错误不当之处 请指出 概述 Flume用于离线日志的 采集与传输 Agent 单台机器a1
  • 分析jdk 1.8 ConcurrentHashMap 的put 方法

    如有转载请注明出处https blog csdn net weixin 41955327 article details 90228701 public V put K key V value 点击进入putVal return putVa
  • Nacos 2.0.3 源码启动

    下载源码 git clone https github com alibaba nacos git 然后切换分支 到 2 0 3 git checkout 2 0 3 nacos 工程目录 INFO Alibaba NACOS 2 0 3
  • vue项目点击侧边栏刷新页面

    在很多vue后台demo中都会有点击侧边栏刷新页面的需求 在很多演示中对这部分并没有太多的考虑 今天作者就对该部分的代码设计做一个介绍 首先直接上代码 修改路由分发的部分 在app vue或者具体项目中路由分发的地方引入v if isRou
  • python读取csv文件_python配置文件的读取

    本文主要分享下python中如何读取配置文件 1 首先我们要了解什么是配置文件 2 配置文件就是项目使用的常量 我们把它们放在一个文件里面 一般以 ini conf xml yaml等结尾 比如 test conf product yaml
  • Mol Cell Proteomics.

    期刊 Mol Cell Proteomics 题目 Celastrol protects from cholestatic liver injury though modulation of SIRT1 FXR signaling 通讯作者
  • 解决wso2 axis2server 跑不起

    wso2ei 6 1 0 运行axis2server报下面找个错误 Server could not start due to class loading issue java lang NoSuchMethodException samp
  • Anaconda安装过程

    Anaconda的安装与配置 1 1 下载Anaconda 网址 https www anaconda com download 1 2 安装教程 网址 https blog csdn net ITLearnHall article det
  • 爬虫工具之Beautiful Soup学习

    参考 Python技能树共建 Beautiful Soup 梦想橡皮擦的博客 CSDN博客 Beautiful Soup主要用于将 HTML 标签转换为 Python 对象树 然后让我们从对象树中提取数据 基础用法 import reque
  • 浅谈在Angular项目中怎么从RESTful API转移到Graphql API

    你了解 GraphQL 吗 简单的说 GraphQL是一个开源的查询语言和协议API GraphQL API是基于REST架构的现代化替代者 不同于REST GraphQL允许客户端根据其需要请求特定的部分数据 这与请求固定数据结构的方式不
  • 一道有趣的GOOGLE面试题——找出至少一个重复元素

    一道有趣的GOOGLE面试题 找出至少一个重复元素 题目 一个大小为n的数组 里面的数都属于范围 0 n 1 有不确定的重复元素 找到至少一个重复元素 要求O 1 空间和O n 时间 这个题目要求用O n 的时间复杂度 这意味着只能遍历数组