六角幻方--详解第五届蓝桥杯大赛Java本科B组决赛真题

六角幻方

题目要求:

把 1 2 3 ... 19 共19个整数排列成六角形状,如下:



    * * *
   * * * *
  * * * * *
   * * * * 
    * * *

要求每个直线上的数字之和必须相等。共有15条直线哦!

再给点线索吧!我们预先填好了2个数字,第一行的头两个数字是:15 13,参见下图


黄色一行为所求。

请你填写出中间一行的5个数字。数字间用空格分开。

这是一行用空格分开的整数,请通过浏览器提交答案,不要填写任何多余的内容(比如说明性的文字等)


解析:这是一道典型的dfs(深度优先搜索)算法题目,可是细想一下,还有问题!题目要求15条直线上每条直线数字之和相等,这个要怎样判断?这19个位置又要怎样存储?二维数组?可是每行长度不一致!

一开始确实有点摸不着头脑,小弟不才,下面我说一下我的方法,未必是最优解法,但是能解出来,算是抛砖引玉,哪位大牛如果有更优解法,欢迎交流大笑


先说下我的大致思想,然后再上代码:

1.首先这19个数存在一个5*5的二维数组中,尽管除了第3行其他行后面都会空出一或两个位置,但是这并不影响最终结果

2.使用常规dfs算法,依次递归求解,递归过程中注意在合适的位置加以判断(判断每行的累加和是否相等),判断结果可以用来“剪枝”(具体解释看代码)

3.通过dfs算法,依次填入数字到数组中,过程中一旦满足某一行数字已经填满,那就直接判断当前行的累加和是否和其他行一样,若满足则继续递归寻找下一个,不满足就没必要继续递归,注意一定要及时判断,而不是留到最后再判断,这样便于降低时间复杂度


下面是代码,代码注释很多,请仔细看注释:

package _2014年国赛B组;

public class 第二题 {

	private static boolean vis[] = new boolean[19];
	private static int sum = 0;
	
	public static void main(String[] args) {
		int[][] num = new int[5][5];
		num[0][0] = 15;
		num[0][1] = 13;
		vis[14] = true;
		vis[12] = true;
		//这里表示第1行第3列开始dfs
		dfs(num,0,2);
	}
	
	
	/**
	 * 这个是递归实现dfs的方法
	 * @param num 存储的数组
	 * @param x 当前正在查找的行
	 * @param y 当前正在查找的列
	 */
	private static void dfs(int[][] num, int x, int y) {
		if(x==5&&y==0){
			//这里打印最终结果!
			syso(num);
			return;
		}
		//依次从1循环到19
		for(int i = 1;i<20;i++){
			//如果当前数字i已经被使用,直接continue,加快循环
			if(vis[i-1]){
				//这里vis下标之所以i-1,是因为i从1开始,所以vis数组下标从0到18分别依次对应i的值为1到19!
				//这是第一次“剪枝”但是这种情况下算法时间复杂度依然很高,必须再次剪枝(在下方)
				continue;
			}else{
				//当前数字i没有被占有,那就把数字给num数组,并且让对应的vis数组为true
				//表示数字i被占有,防止其他位置再次使用该数字
				vis[i-1] =true;
				num[x][y] = i;
				//这里测试当前第x行第y列数组放数字i时,数组是否满足题目条件
				test(num,x,y);
				//测试完表示当前数字i已经使用完毕,把vis数组归位
				vis[i-1] = false;
			}
		}
		
	}

	private static void test(int[][] num, int x, int y) {
		//这里需要分情况讨论,首先每行的列数不一样,但是明显有规律
		//规律就是第x行最多有4-|x-2|列(数学规律,很容易观察出)
		//所以需要判断当前y值与4-|x-2|的大小关系,如果y<4-|x-2|
		//说明当前列还不是这一行的最后一列,那下次递归就是dfs(num,x,y+1);
		//num表示当前数组,x是行,y+1是列
		if(y<4-Math.abs(x-2)){
			//这里当y<4-|x-2|时,需要分情况看,分别是x =2,3,4时要特殊判断
			if(x==2){
				//x=2,y=0时,判断最左上那条直线是否满足累加和与其他直线相等
				if(y==0){
					//如果相等那就递归下一列
					if(num[0][0]+num[1][0]+num[2][0]==sum){
						dfs(num,x,y+1);
					}//这里没有写else,因为当不满足上面if条件时已经没必要继续递归下一列了
					//所以直接退出test即可,这里可以加个return,更好理解(其实不加也是直接结束了test,下面的ifelse都是没有机会执行的)
					//这是第二次“剪枝”,加快循环进行
				}else{//这里else是跟着if(y==0)的,不要搞错!
					dfs(num,x,y+1);
				}
			}else if(x==3){
				//x=3,y=0同上,不再解释
				if(y==0){
					if(num[0][1]+num[1][1]+num[2][1]+num[3][0]==sum){
						//这里if满足条件,继续递归,不满足就相当于直接return,这是第3次“剪枝”
						dfs(num,x,y+1);
					}
				}else{//这里else也是跟着if(y==0)的,不要搞错!
					dfs(num,x,y+1);
				}
			}else if(x==4){
				//x=4,y=0同上,不再解释
				if(y==0){
					//这里if满足条件,继续递归,不满足就相当于直接return,这是第4次“剪枝”
					if(num[2][0]+num[3][0]+num[4][0]==sum&&num[0][2]+num[1][2]+num[2][2]+num[3][1]+num[4][0]==sum){
						dfs(num,x,y+1);
					}
				}else if(y==1){
					//这里if满足条件,继续递归,不满足就相当于直接return,这是第5次“剪枝”
					if(num[1][0]+num[2][1]+num[3][1]+num[4][1]==sum&&num[1][3]+num[2][3]+num[3][2]+num[4][1]==sum){
						dfs(num,x,y+1);
					}
				}else{
					dfs(num,x,y+1);
				}
			}else{//这里else表示当x=0,1时直接递归
				dfs(num,x,y+1);
			}
		}else{//else表示当前行的列数已经足够,要换行了
			//如果x=0,表示当前第1行已经填满,可以计算出第一行的累加和,便于后续和这个累加和比较
			if(x==0){
				sum = num[0][0]+num[0][1]+num[0][2];
				dfs(num,x+1,0);
			}else{//else表示当x!=0时,即当前不是第一行填满时要计算当前行的累加和
				//这里计算第x行累加和,存入temp中(这里直接把5列都加上了,实际也可以加个判断,只加前4-|x-2|列)
				int temp = num[x][0]+num[x][1]+num[x][2]+num[x][3]+num[x][4];
				//如果相等,进一步判断,否则相当于return(尽管我没明写return,但逻辑上是一样的)
				if(sum==temp){
					if(x==2){//x=2表示当前第3行已经填满,需要判断六边形右上角直线是否满足要求
						if(num[0][2]+num[1][3]+num[2][4]==sum){
							//这里if满足条件,继续递归,不满足就相当于直接return,这是第6次“剪枝”
							dfs(num,x+1,0);
						}
					}else if(x==3){//x=3同理,判断右上第二条直线
						if(num[0][1]+num[1][2]+num[2][3]+num[3][3]==sum){
							//这里if满足条件,继续递归,不满足就相当于直接return,这是第7次“剪枝”
							dfs(num,x+1,0);
						}
					}else if(x==4){//x=4同理,这里需要判断两条直线
						if(num[2][4]+num[3][3]+num[4][2]==sum&&num[0][0]+num[1][1]+num[2][2]+num[3][2]+num[4][2]==sum){
							//这里if满足条件,继续递归,不满足就相当于直接return,这是第8次“剪枝”
							dfs(num,x+1,0);
						}
					}else{//这个else表示x=1时直接递归下一层
						dfs(num,x+1,0);
					}
				}
			}
		}
	}

	/**
	 * 这个方法就是循环输出最终结果
	 * @param num 待输出的数组
	 */
	private static void syso(int[][] num) {
		StringBuilder sb = new StringBuilder(); 
		for(int i = 0;i<5;i++){
			for(int j = 0;j<5-Math.abs(i-2);j++){
				//这里题上没有严格要求行末不能有空格,所以就没有判断直接输出了
				sb.append(num[i][j]).append(" ");
			}
			sb.append("\n");
		}
		System.out.println(sb.toString());
	}

}
输出结果如下:
15 13 10 
14 8 4 12 
9 6 5 2 16 
11 1 7 19 
18 17 3 

上面就是全部代码,该加的注释,我都加的非常详细了最后总结下,这道题就是在原来dfs的基础上多加几个判断罢了,添加判断时一定要细心,还要记得及时剪枝,加快循环,可以快速得出结果

实测我的机器是1600ms左右出结果,实际上这道题目,一开始我并没有剪枝,导致点了运行程序就去吃饭去了,半个小时后回来居然还没有运行完再见所以剪枝操作一定要加上,毕竟这19个数字的全排列时间复杂度是O(n!),所以越早判断,越早剪枝,越容易加快速度,降低时间复杂度,得出结果!




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值