【算法题集锦之一】--给定只能排序5个数的方法,找出25个数中的前四个

来源:互联网 发布:mac版lol官网 编辑:程序博客网 时间:2024/06/06 19:03

转眼间大学毕业了,找工作的过程不是很顺利,虽然也面过几家知名的互联网企业(阿里,PPTV等)且坚持到了终面,但还是以失败告终,除了运气成分之外,更多的还是因为自己的算法钻研的不够深,故产生了这个想法:通过写一系列关于算法的博客来提高自己的算法水平,希望自己能坚持下去。

这道题目是我在面试PPTV时,面试官出的一道题,据说是原百度的校招题:即25匹赛马,只有五条跑道,问需要比赛几次才能确定前5匹马。

不过面试官换了一种描述:

给定一个方法sort,每次最多排序5个数,
只使用该排序方法,给定25个无序的数,
问需要调用sort方法几次可以得到前4个数。

这道题难度适中,不算很难,一般都能得到一种解法,但是得到最优解还是要思考一下的。

25个数需要得到前4个数,不管怎样这25个数肯定都要比较一遍,所以第一步的话很容易想到:将25个数分成五组,对五组分别进行排序,得到五组有序的数组。

比较容易想到的一种解法如下:

5组数组都已经有序了,每一组的最大数都是已知了,接下来只需要取每组的第一个数进行排序,就能获得一个要求的数(逐个获取最大的数),去除这个数,然后再如此反复比较(即每一次都取每组中的第一个数进行排序,就能依次获得第二个数,第三个数,和第四个数),这样总共需要9次排序。下面的图说明了一种可能出现的排序情况




同理,最后一次排序(即第九次排序)也是重复这个过程,到第九次就可以获得前四个数了。

当然,九次排序明显不是最优解,因为在这九次排序中我们做了多余的比较,注意到题目只需要取前四个数,当第六次排序后。我们就可以排除掉一些不可能的数据,很显然,第五组中的数是可以排除的,因为第五组中最大的数也只可能是第五大的数(前四组的第一个数都比它大),同理,第四组的后四个数也是不可能的,第三组的最后三个,第二组的最后两个和第一组的最后一个数,这些数都可以排除。

排除了这些数以后,我们就可以减少排序次数了,第六次排序后可以排除:1、已经确定的最大的数 2、上面列举出来的数(共15个)

这样第六次排序后还需要比较的数只剩下9个了,如下图。


剩下的9个数中还需要比较出前三个数来,只需要两次排序即可。

 第七次排序,取第一组的第一个,第一组的第二个,第二组的第一个、第三组的第一个和第四组的第一个进行排序。第七次排序后可以排除最后两个数字所在组的所有数(因为只需要前三个)。

因为第二组的第一个数、第三组的第一个数和第四组的第一个数是有序的,所以最后两个数只可能有三种情况:1、都属于第一组

2、第三组和第四组

3、第一组的第二个数和最后一组


这样经过七次排序可以排除至少三个数,加上第七次排序找出来的第二大的数,可以排除4个数,则只剩下5个数,再进行一次排序即可,总共八次排序。

下面是用代码实现的过程(java),以快排作为给定的排序方法。

package test;import java.util.HashMap;import java.util.Map;import java.util.Random;public class Test {/** * 快排 * @param num */public static void sort(int[] num, int low, int high){if(low < high){// 获得中轴位置int middle = hoare(num, low, high);// 左边排序sort(num, low, middle - 1);// 右边排序sort(num, middle + 1, high);}}/** * 快排核心算法 * @param num 数组 * @param low 低位指针 * @param high 高位指针 * @return */public static int hoare(int[] num, int low, int high){int key = num[low];//最低位为关键数while(low < high){// 高位指针一直递减  直到遇见比key小的数while(num[high] <= key && low < high){high --;}//  把高位小的数移到低位num[low] = num[high];// 低位指针一直递加 直到遇见比key大的数while(num[low] >= key && low < high){low ++;}// 把低位大的数移到高位num[high] = num[low];}// 当循环停止(即low == high)的时候,到达中轴位置,中轴位置的值置为keynum[low] = key;// 返回中轴位置return low;}/** * 问题描述: * 给定一个方法sort,每次最多排序5个数, * 只使用该排序方法,给定25个无序的数, * 问最少调用sort方法几次可以得到前四个数 * @param args */public static void main(String[] args) {int[] num = new int[25];// 随机生成25个数for(int i = 0; i < 25; i ++){num[i] = new Random().nextInt(100);}System.out.println("未排序的情况");for(int i = 0; i < 5; i ++){System.out.print("第" + (i+1) + "组:");for(int j = i * 5; j < (i + 1) * 5; j ++)System.out.print(num[j] + " ");System.out.println();}// 分五组排序(前五次)for(int i = 0; i < 5; i ++){sort(num, i * 5, (i * 5 + 4));}// 记录每一组最大数的位置Map<Integer, Integer> map = new HashMap<Integer, Integer>();map.put(num[0], 0);map.put(num[5], 5);map.put(num[10], 10);map.put(num[15], 15);map.put(num[20], 20);System.out.println("前五次排序后的情况");for(int i = 0; i < 5; i ++){System.out.print("第" + (i+1) + "组:");for(int j = i * 5; j < (i + 1) * 5; j ++)System.out.print(num[j] + " ");System.out.println();}// 第六次排序(取五组的最大的数排序)int[] temp = {num[0], num[5], num[10], num[15], num[20]};sort(temp, 0, 4);// 本次排序后可以排除第五组全部数字、第四组最后四个,// 第三组最后三个,第二组最后两个,第一组最后一个。// 取得每一组的第一个数在原数组中的位置int indexOfFirst = map.get(temp[0]);int indexOfSecond = map.get(temp[1]);int indexOfThird = map.get(temp[2]);int indexOfForth = map.get(temp[3]);int indexOfFifth = map.get(temp[4]);// 第六次排序后的情况System.out.println("第六次排序未排除数后的情况:");System.out.println("第1组:"+ num[indexOfFirst] + " "+ num[indexOfFirst + 1] + " " + num[indexOfFirst + 2] + " " + num[indexOfFirst + 3] + " " + num[indexOfFirst + 4]);System.out.println("第2组:"+ num[indexOfSecond] + " "+ num[indexOfSecond + 1] + " " + num[indexOfSecond + 2] + " " + num[indexOfSecond + 3] + " " + num[indexOfSecond + 4]);System.out.println("第3组:"+ num[indexOfThird] + " "+ num[indexOfThird + 1] + " " + num[indexOfThird + 2] + " " + num[indexOfThird + 3] + " " + num[indexOfThird + 4]);System.out.println("第4组:"+ num[indexOfForth] + " "+ num[indexOfForth + 1] + " " + num[indexOfForth + 2] + " " + num[indexOfForth + 3] + " " + num[indexOfForth + 4]);System.out.println("第5组:"+ num[indexOfFifth] + " "+ num[indexOfFifth + 1] + " " + num[indexOfFifth + 2] + " " + num[indexOfFifth + 3] + " " + num[indexOfFifth + 4]);// 第六次排序后的情况System.out.println("第六次排序排除数后的情况:");System.out.println("第1组:" + num[indexOfFirst + 1] + " " +num[indexOfFirst + 2] + " " + num[indexOfFirst + 3]);System.out.println("第2组:" + num[indexOfSecond] + " " +num[indexOfSecond + 1] + " " + num[indexOfSecond + 2]);System.out.println("第3组:" + num[indexOfThird] + " " +num[indexOfThird + 1]);System.out.println("第4组:" + num[indexOfForth]);// 第六次排序后可以得到最大的数---即第一组的第一个数int first = num[indexOfFirst];int second;// 剩余的数分布情况// 第一组的三个数(第一个数已经确定为最大)// t1: {num[indexOfFirst + 1], num[indexOfFirst + 2], num[indexOfFirst + 3]};// 第二组的前三个// t2: {num[indexOfSecond], num[indexOfSecond + 1], num[indexOfSecond + 2]};// 第三组的前两个// t3: {num[indexOfThird], num[indexOfThird + 1]};// 第三组的第一个// t4: {num[indexOfForth]};// 现在只剩下9个数,要得到这9个数中前三个,还需两次排序即可// 理由:// 第七次排序,取第一组的第一个,第一组的第二个,第二组的第一个、第三组的第一个和第四组的第一个进行排序// 第七次排序后可以排除最后两个数字所在组的所有数(因为只需要前三个)// 又第二组的第一个(num[indexOfSecond])、第三组的第一个(num[indexOfThird])// 第四组的第一个(num[indexOfForth])是有序的,最后两个数中不可能有第二组的数,// 所以排序完后最后两个数只可能有三种情况。// 1、都属于第一组 2、第三组和第四组 3、第一组的第二个数(num[indexOfFirst + 2])和最后一组// 其他情况都是不可能的,这三种情况都能排除至少3个数,加上第七次排序得到的最大数(即第二大的数),// 则剩下的数最多只有5个,再一次排序即可// 记录接下来要排序的五个数属于哪一组map.put(num[indexOfFirst + 1], 1);// 第一组的第一个map.put(num[indexOfFirst + 2], 1); // 第一组的第二个map.put(num[indexOfSecond], 2); // 第二组的第一个map.put(num[indexOfThird], 3); // 第三组的第一个map.put(num[indexOfForth], 4); // 第四组的第一个temp = new int[]{ num[indexOfFirst + 1],   num[indexOfFirst + 2],  num[indexOfSecond],   num[indexOfThird],  num[indexOfForth]  };//第七次排序sort(temp, 0, 4);//第七次排序后可以排除剩余两个数所在组的全部数字,//且可以找出第二大的数,即这五个数中的第一个second = temp[0];//获得最后两个数所在的组int groupOfForth = map.get(temp[3]);int groupOfFifth = map.get(temp[4]);//第一个数所在的组int groupOfFirst = map.get(temp[0]);// 第一种情况:都属于第一组if(groupOfForth == 1 && groupOfFifth == 1){//最后两个数都属于第一组,则最大数是第二组的第一个,即num[indexOfSecond]//排除第一组的所有数字和第二组的第一个,剩余的数为第二组的第二、三个//第三组所有、第四组所有temp = new int[]{num[indexOfSecond + 1],  num[indexOfSecond + 2], num[indexOfThird],  num[indexOfThird + 1], num[indexOfForth]};// 第八次排序,取前两个数即可sort(temp, 0, 4);}// 第二种情况 、最后两个数属于第三组和第四组else if(groupOfForth == 3 && groupOfFifth == 4){//排除第三组和第四组的数//第一个数有可能属于第一组的第一个,或者第二组的第一个if(groupOfFirst == 1){//如果第一个数属于第一组的第一个,则剩余要排序的数是第一组的后两个//第二组的全部temp = new int[]{num[indexOfFirst + 2],  num[indexOfFirst + 3], num[indexOfSecond], num[indexOfSecond + 1],  num[indexOfSecond + 2], };// 第八次排序sort(temp, 0 , 4);} else if(groupOfFirst == 2){//如果第一个数属于第二组第一个,则剩余要排序的数为第一组的全部//第二组的剩余两个数temp = new int[]{num[indexOfFirst + 1],  num[indexOfFirst + 2],  num[indexOfFirst + 3], num[indexOfSecond + 1],  num[indexOfSecond + 2], };// 第八次排序sort(temp, 0 , 4);}}// 第三种情况,最后两个数是第一组的第二个和最后一组else if((groupOfForth == 1 && groupOfFifth == 4) || (groupOfForth == 4 && groupOfFifth == 1)){//排除第一组的后三个数和第四组的数//第一个数有可能属于第一组的第一个,或者第二组的第一个if(groupOfFirst == 1){//如果第一个数属于第一组的第一个,则剩余要排序的数是第二组的全部//第三组的全部temp = new int[]{num[indexOfSecond], num[indexOfSecond + 1],  num[indexOfSecond + 2], num[indexOfThird],  num[indexOfThird + 1], };// 第八次排序sort(temp, 0 , 4);} else if(groupOfFirst == 2){//如果第一个数属于第二组第一个,则剩余要排序的数为第一组的第一个//第二组的剩余两个数,和第三组的全部temp = new int[]{num[indexOfFirst + 1],  num[indexOfSecond + 1],  num[indexOfSecond + 2], num[indexOfThird],  num[indexOfThird + 1], };// 第八次排序sort(temp, 0 , 4);}}System.out.println("前四个数已找到,分别为:" +  first + " " +  second + " " +  temp[0] + " " + temp[1]);}}



0 0