您的位置:首页 > 职场人生

Google面试一道排序题

2015-09-05 23:33 375 查看
今年有同学在面试Google实习生的时候碰到一道题目,大半年过去了,等到现在才发出来,原意也是不希望泄露题库,可以互相交流。

题目是:把一组不重复的数组排序,希望最后达到计数项都小于相邻的偶数项,也就是说要达到a(2i - 1) > a(2i) < a(2i + 1)的情况。

思路一:乍一看,这个题目可以用很简单的普通排序做出来,用快排或其他排序算法可以达到O(nlgn)的时间复杂度,然后再把大的那一部分全部都填入偶数项,小的那一部分全都填入奇数项,即可达到目标。

代码如下:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

vector<int> googleSort(vector<int> vv){
	sort(vv.begin(), vv.end());//用标准库中排序递增排序vv,时间复杂度O(nlgn)
	vector<int>ret(vv.size());
	int idx = 0;//从前向后取vv中的值
	for (int i = 0; i < ret.size(); i += 2){//把小的部分全都放在奇数项,时间复杂度O(n)
		ret[i] = vv[idx ++];
	}
	idx = vv.size() - 1;//从后向前取vv中的值
	for (int i = 1; i < ret.size(); i += 2){//把大的部分全都放在偶数项
		ret[i] = vv[idx--];
	}
	return ret;
}
void print(vector<int> vv){//格式化输出
	for (int i = 0; i < vv.size(); ++i){
		if (i)cout << " " << vv[i];
		else cout << vv[i];
	}
	cout << endl;
}

int main(){
	vector<int>V = { 1, 7, 9, 0, 5, 3, 2, 6, 8, 4 };//假设有这些数据
	vector<int>res = googleSort(V);
	print(res);
	return 0;
}
运行如下:



虽然这样可以,但是这样肯定不能满足面试官要求。

如何才能用更好更快的方法呢?

思路二:

可以不用先全部排序吗,其实从上面的例子中,我们可以看到,我们是要把数据分成大的和小的两部分,相当于找到中位数,那么我们只要找到第k大个数,其中k为n / 2,把小于第k大的数全都放在奇数项,把大于等于第k大的数全都放在偶数项。就能达到目标。

其中用到找到第k大数的算法了,是很经典的算法了,我参考了http://www.cnblogs.com/luxiaoxun/archive/2012/08/06/2624799.html

其实它利用了快排的思想(更准确得说快排和找第k大数都用了相同的思想),用前面一个数把数组分成小于它和大于它的两部分,当我们计数后发现前一部分或后一部分少了的时候,再递归对后一部分和前一部分找第k'(一个换算过程)大个数。

虽然,找第k大数算法的时间复杂度是O(n),不过还是略微麻烦,思路三会说到。

代码如下:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int partition(vector<int>& vv,int start, int end){//为简化算法,这里没有使用随机挑选k
			//划分函数,其中start表示开始的位置,end表示结束位置
	int idx_s = start;
	int x = vv[start];
	end = end + 1;
	while (1){
		while (vv[++start] > x);
		while (vv[--end] < x);
		if (start >= end)break;
		int tmp = vv[start];
		vv[start] = vv[end];
		vv[end] = tmp;
	}
	vv[idx_s] = vv[end];
	vv[end] = x;
	return end;
}
int findKth(vector<int>& vv,int start,int end,int k){//这里多传递一个k的值
	if (start == end)
		return vv[start];
	int i = partition(vv, start, end);
	int j = i - start + 1;
	if (k == j)return vv[i];
	if (k < j)
		return findKth(vv, start, i - 1, k);
	else
		return findKth(vv, i + 1, end, k - j);
}
vector<int> googleSort(vector<int> vv){
	int kth = findKth(vv, 0, vv.size() - 1, vv.size() / 2);
	vector<int>ret(vv.size());
	int i = 0, j = 1;//奇数项和偶数项的下标
	for (int idx = 0; idx < vv.size(); ++idx){//遍历vv
		if (vv[idx] < kth){//把小于kth的数全都放在奇数项,时间复杂度O(n)
			ret[i] = vv[idx];
			i += 2;
		}
		else{//把大于kth的数全都放在偶数项
			ret[j] = vv[idx];
			j += 2;
		}
	}
	return ret;
}
void print(vector<int> vv){//格式化输出
	for (int i = 0; i < vv.size(); ++i){
		if (i)cout << " " << vv[i];
		else cout << vv[i];
	}
	cout << endl;
}

int main(){
	vector<int>V = { 1, 7, 9, 0, 5, 3, 2, 6, 8, 4 };//假设有这些数据
	vector<int>res = googleSort(V);
	print(res);
	return 0;
}
运行结果如下:



思路三:

这里,就有一个“浪费”的思想,其实,题目中只要求我们排成一小一大一小就好,没有必要把大的位置是最大的,就是说1 3 2 6 5 8 4 9 0 7也是可以的,其中3不是最大的大一部分,但是同样可以在偶数项,所以可以知道先全局排序是一种“浪费”,无论是思路1还是思路2,相当于我们先用比较大的力气使它达到了更有序的状态,然后再把它调整回相对无须的状态,得不偿失。

最好是直接就奔着那个目标去,杀鸡不用宰牛刀,把力气花的恰到好处,刚好跑到山顶最好。

所以,我的思路是

(1)先把数组分成奇数项-偶数项,奇数项-偶数项……两个两个分,相当于a奇-a偶,a奇-a偶,……

(2)先对其进行一次排列,按照递增的顺序,使其达到所有的a奇<a偶。

(3)然后在对其分成a奇,a偶-a奇,a偶-a奇……这样的顺序,

(4)对其进行一次排序,按照递减的顺序,使其达到所有a偶>a奇的效果,

这样我们就能满足所有的a奇都小于相邻a偶的效果。

证明如下:

假如有a奇1,a偶1,a奇2,a偶2这四个数,当经过(1)(2)步骤后,会有a奇1<a偶1,a奇2<a偶2,(如果不满足对应的奇和偶会交换)

然后当经过(3)和(4)时,

(a)假设a偶1>a奇2成立,那么不要动,就有a奇1<a偶1>a奇2<a偶2

(b)假设a偶1<a奇2成立,就形成了a奇1<a偶1<a奇2<a偶2,(注意到这里a奇1<a奇2)那么当我们把a偶1和a奇2调换后,肯定有a奇1<a偶1>a奇2<a偶2

所以,我们没有要求所有的奇数项一定是数组中绝对小的那一部分,所以是符合不“浪费”的思想的,也就是其无序状态最大。

相当于思路一是全局排序,思路三是局部排序,思路二是介于两者之间的。

代码如下:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

vector<int> googleSort(vector<int> vv){
	for (int i = 0, j = 1; i < vv.size() && j < vv.size(); i += 2, j += 2){//奇-偶,奇-偶比较
		if (vv[i] > vv[j])
			swap(vv[i], vv[j]);
	}
	for (int i = 1, j = 2; i < vv.size() && j < vv.size(); i += 2, j += 2){//偶-奇,偶-奇比较
		if (vv[i] < vv[j])
			swap(vv[i], vv[j]);
	}
	return vv;
}
void print(vector<int> vv){//格式化输出
	for (int i = 0; i < vv.size(); ++i){
		if (i)cout << " " << vv[i];
		else cout << vv[i];
	}
	cout << endl;
}

int main(){
	vector<int>V = { 1, 7, 9, 0, 5, 3, 2, 6, 8, 4 };//假设有这些数据
	vector<int>res = googleSort(V);
	print(res);
	return 0;
}
运行结果如下:



P.S

从这里可以看到,即使是很简单的一道排序题,都需要花费心思去思考,到底什么样的算法才是最适合它的,八面玲珑的普适算法虽然可以信手拈来,但是,不一定是针对具体一道题的利器。对每一道题的特点分析,才是王道。

——Apie陈小旭
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: