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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

剩下的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)的时候,到达中轴位置,中轴位置的值置为key
		num[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 map = new HashMap();
		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]);
		
	
	}
}



你可能感兴趣的:(算法,算法,面试,校招)