题目要求:
把 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!),所以越早判断,越早剪枝,越容易加快速度,降低时间复杂度,得出结果!