百度一道面试题引发的思考

最近在人人网上看到有同学分享各公司笔试面试题汇总,看到百度有道题,觉得比较有意思。因为题目看很简单,我当时就想百度为什么出这样的问题,因为在编程过程中根本不会遇到样的问题,并且当时给出了一种“打趣”的解法,即下面说到的SumGet方法。

【百度面试题】有101个数,为[1,100]之间的数,其中一个数是重复的,如何寻找这个重复的数,其时间复杂度和空间复杂度是多少?

看着就觉得好笑,出题人怎么会出这样的题?让人觉得出题人没有水平。我觉得像百度这样的大公司,至少这样出才比较合理:有2^31个数,都是在[1, 2^32-1]之间的整数,请写出一个算法,判断这些数中是否有重复的数,如果有,请都输出来;最后给出算法的时间空间复杂度。最近不是都流行大数据,大处理量吗?什么竞赛不也是这样训练的吗?

扯得有点远,还是回到这道题上来吧。我觉得学计算机的肯定对排序是特别熟悉,据说%之80到90的问题都可以用排序来解决,难怪有那么多排序方法。我想一般人遇到百度这道题,都会想到用排序方法(%之8910的成功率放在那不用,傻啊!)。排序方法有很多,如何选择那就看个人擅长了,反正我是比较晕。如果我记得不错的话,像这种初始顺序都没定的排序,时间复杂度至少是o(nlgn),空间复杂度至少是o(n)。诚然排序是万能的方法,但是万能的方法不一定是好方法。因为这样的方法往往会忽略问题的本质,而对问题来说,本质才是最重要的,才是解决问题的关键。

下面我将写写我对这道题的本质思考得到的三种方法(以下方法都对应函数的名字)。

一、SumGet方法

这个题说只有一个数是重复的,那撇开这个数,也就是说1到100的数都会出现,于是我把所有101个数相加起来,最后再减去1到100的和,不就出来了吗?

这种方法的时间和空间复杂度都是o(n)。这种方法也就是投机取巧而已,虽然解决了问题,而且似乎还不错,但是没有什么推广性,下次百度出:2^32-1个数,为[1,2^32-1]之间的数,其中一个数是重复的,……你就傻眼了,因为要溢出了。虽然对溢出做一些处理也是可以用的。

二、FlagGet方法

做标记是一个好方法。可以创建一个数组flags[100],初始化各元素都为0,然后对要求数组遍历,将flags数组对应的元素置为1,如遍历到50这个数了,那么就令flags[50-1] = 1。于是在遍历过程中看flags对应元素是否为1,如果为1,说明已经找到,可以结束了。

这种方法的时间和空间复杂度也是o(n)。不足之处就是要申请一个数组,如果数组比较大的话就完了。

三、JumpGet方法

这种方法是对FlagGet方法的改进,不用创建数组,唯一的区别在于它将原数组改变了。

有点不太好说,举例子比较形象化。比如初始数组为numbers,numbers[23]值为71,那么我就jump到numbers[71-1]处,得知它的值为15,那么将numbers[71-1]令为0,再jump到numbers[15-1]处,如此反复,直到jump到一个为0的位置,说明之前已经jump来了,在jump到这里,就说明我这次jump的数字就是那个重复的数字。等等,有个问题。如何选择jump的第一个位置呢?如果选择的numbers[23]值为24,那不是原地jump吗?而且有可能出现这样的循环,如:numbers[23]=71,numbers[70]=15,numbers[14]=24。其实,有一个位置是其他位置都jump不到的,那就是numbers[100],因为没有数会等于101。所以以这个位置为初始点是最好的,它不会回来,那种循环的点它又进不去,那就只有一种可能,通过不同的位置jump到同一位置。于是找到答案了。

这种方法的时间和空间复杂度也是o(n)。

附上源代码吧。

#include <iostream>
#include <algorithm>
#include <ctime>
#include <cstdlib>

#define NUM 100

using namespace std;

int SumGet(int numbers[NUM+1])
{
	int sum = 0;
	for (int i = 0; i <= NUM; i++)
		sum += numbers[i];
	return (sum-(NUM+1)*NUM/2);
}

int FlagGet(int numbers[NUM+1])
{
	int flags[NUM+1];
	int num;
	int searchNums = 0;
	for (int i = 0; i <= NUM; i++)
		flags[i] = 0;
	for (int i = 0; i <= NUM; i++) {
		if (flags[numbers[i]-1] == 1) {
			num = numbers[i];
			break;
		}
		searchNums++;
		flags[numbers[i]-1] = 1;
	}
	cout << "searchNums = " << searchNums << endl;
	return num;
}

int JumpGet(int numbers[NUM+1])
{
	int jumpto = numbers[NUM];
	int lastjump = 0;
	int jumpNums = 0;
	while (jumpto != 0) {
		lastjump = jumpto;
		jumpto = numbers[lastjump-1];
		numbers[lastjump-1] = 0;
		jumpNums++;
		cout << "-->" << lastjump;
	}
	cout << endl << "jumpNums = " << jumpNums << endl;
	return lastjump;
}

int main()
{
	int numbers[NUM+1];
	int num = 0;
	for (int i = 0; i <= NUM; i++)
		numbers[i] = i+1;
	while ( num <= 0 || num > NUM) {
		cout << "input a number between 1 to 100 :";
		cin >> num;
	}
	numbers[NUM] = num;
	srand(unsigned(time(NULL)));
	random_shuffle(numbers, numbers+NUM+1);
	for (int i = 0; i <= NUM; i++)
		cout << numbers[i] << " ";
	cout << endl;
	cout << "SumGet get :" << SumGet(numbers) << endl;
	cout << "FlagGet get :" << FlagGet(numbers) << endl;
	cout << "JumpGet get :" << JumpGet(numbers) << endl;
	return 0;
}

最后,我想说:百度这道题出得还是蛮好的。不知道有没有这样的寓意,就是要从百个千个的应聘者中找出这么一个出来。我还是想抱怨一下,我们做的难题还不够吗,我们不就是从难题中爬出来,进了大学,NM出来了还让我们做难题,让不让人活啊!

Linux之父Linus说的好,“做简单的事,做到完美”。也不太确定是不是他说的,确实很有道理。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值