黑马程序员技术交流社区

标题: 螺旋数字挑战题提升级版 [打印本页]

作者: 戴振良    时间: 2013-5-5 16:15
标题: 螺旋数字挑战题提升级版
这是上次出的一道螺旋状挑战题,今天我把它升级了,先看效果,看完后大家有没木有要挑战一下的冲动,大家可以先尝试自己做,做不出来时再参考我后面的代码,代码也不多,80行左右,先看效果:
一、顺时
       1
、数字先上移

int[][] intArray = fillIntArray(2, true, Direction.UP);//获取螺旋状的int数组
drawCustomShape(intArray);
这里数字1相对于左下角的坐标是对称的,也就是说如果它00点如果是在左下角,那么也可以用公式:(n + 1) / 2 – 1,拿到这个左下角的坐标,而我们是知道之个坐标实际开始为位置为左上角的00位置,而坐标的长度又是固定的,所以可以计算出来。
2、数字先下移
3、数字先左移
*  *  *  *
*  3  4  *
*  2  1  *
*  *  *  *
4
、数字先右移,上次的挑战题就是这一种情况
*  *  *  *
*  1  2  *
*  4  3  *
*  *  *  *
二、逆时
1
、数字先上移
*  *  *  *
*  3  2  *
*  4  1  *
*  *  *  *
2
、数字先下移
*  *  *  *
*  1  4  *
*  2  3  *
*  *  *  *
3
、数字先左移
*  *  *  *
*  2  1  *
*  3  4  *
*  *  *  *
4
、数字先右移
*  *  *  *
*  4  3  *
*  1  2  *
*  *  *  *

思路:
看了别人的代码,看到某些人的代码很少就完成了,于是我再想其他的思考方式,还是想到了,估计也是大多数人用的方式,如果不懂的可以看看:
分析:
从数字1开始:
1、  右移放2
2、  下移放3
3、  左移放4、左移放5
4、  上移放6、上移放7
5、  右移放8、右移放9、右移放10
6、  下移放11、下移放12、下移放13
7、  。。。
通过上面的数据可以发现,是按右、下、左、上的顺序循环存放数据的。存放数据时的移动规律为:
右移1次,下移1次
左移2次,上移2次
右移3次,下移3次
。。。依此类推
可以看到每连续的两个方向他们移动的格子数量是一样的,而且每换两个方向后移动的次数加1,这样可以用一个循环,每循环两次,就把一个方向要移动的格子数加1,代码如下:
int moveCount= 1; //moveCount用来做每一个方向的移动步数的循环次数,刚开始右移、下移都是只移动一步
for (int i=1;; i++) {
      。。。其他代码
if(i%2==0) moveCount++;
}
每个方向的移动步数又用一个循环来完成即可。
最终代码见下一页:


作者: 戴振良    时间: 2013-5-5 16:16
再说说坐标移动的原理:
[attach]18234[/attach]
上图的表格用二维数组表示为:int[][] intArray = new int[5][5]; 第一个下标代表y轴,第二个下标代表x轴,假如1是放在坐标11的位置,完成螺旋状的步骤如下:
1、首先确定数字1的位置为 1  1
2、右移放2,右移即x轴加1,用代码表示为intArray[1][1+1] = 2;
3、下移放3,下移即y轴加1,用代表码示为intArray[1+1][2] = 3;
4、左移放4,左移即x轴减1,用代码表示为intArray[2][2-1] = 4;
4、左移放5,左移即x轴减1,用代码表示为intArray[2][1-1] = 5;
。。。依此类推
如用变量来移动数组的下标代码为:
switch(direction) {
   case UP:    intArray[--y][x] = num++; break; // 上,Y坐标减1
   case DOWN:  intArray[++y][x] = num++; break; // 下,Y坐标加1
   case LEFT:  intArray[y][--x] = num++; break; // 左,X坐标减1
   case RIGHT: intArray[y][++x] = num++; break; // 右,x坐标加1
}
现在最关键的就是先找出1的坐标位置了,自已多画几个图,多看看就能找到规律了,如果是顺时针先右移,那么1的坐标中x轴与y轴的值相等。假如要求n的螺旋图,则1的位置它的计算公式为:(n + 1) / 2 - 1
后来我想到要做一个全能的,就是上面说的两种大变化8种微变化,其实最关键的是要找到1的位置,其它的万变不离其宗,通过观察,我发1的位置都是相对某个角的坐标是对称的,有思路的可以自己挑战一下,实在做不出来可以看我后面的代码,代码并不多80行左右,效果如下,如果你觉得看了有思路了就自己做一下再看我代码吧:

下面代码是看了论坛里陈圳的代码,我加以修改完成的8种变化,它的那个枚举用的实在是太漂亮了!
  1. public static void main(String[] args) {
  2.                 int n = 2;
  3.                 int[][] intArray = fillIntArray(n, false, Direction.RIGHT);//获取螺旋状的int数组
  4.                 drawCustomShape(intArray);
  5.                 //System.out.println("\n=========================================\n");
  6.                 //drawCustomShape(null);        //打印一个空框
  7.         }

  8.         /**
  9.          * 填充螺旋整形数组的方法
  10.          * @param n 指示数据的长度
  11.          * @param isClockwise 指示是否顺时针显示
  12.          * @param direction 指示先向哪个方向移动
  13.          * @return 螺旋整形数组
  14.          */
  15.         public static int[][] fillIntArray(int n, boolean isClockwise, Direction direction) {
  16.                 if (n <= 0) throw new IllegalArgumentException("参数n必须是大于或等于1的整数");
  17.                 int[][] intArray = new int[n][n];
  18.                 int max = n * n;                         // 最大输出数,可用来做结束循环的条件
  19.                 int num = 1;                                 // 当前要输出的数,默认从1开始
  20.                 int x;         // 当前要输出的数字在X轴的位置
  21.                 int y;         // 当前要输出的数字在Y轴的位置
  22.                
  23.                 //根据各种情况初始化数字1在数组中的x、y坐标
  24.                 int upleft = x = y = (n + 1) / 2 - 1;        //顺时针先向右、或逆时针先向下 的情况下 1的坐标相对左上角对称
  25.                 if(n%2==1) {
  26.                         // n是单数的情况下,数字1在整个图形的正中间,算法跟顺时针先向右一样
  27.                 } else if ((isClockwise && direction==Direction.UP) ||(!isClockwise && direction==Direction.RIGHT)) {         // 顺时先向上或逆时先向右
  28.                         y = n - upleft - 1;
  29.                         x = n - upleft - 2;
  30.                 } else if ((isClockwise && direction==Direction.DOWN) ||(!isClockwise && direction==Direction.LEFT)) {        // 顺时先向下或逆时先向右
  31.                         y = n - upleft - 2;
  32.                         x = n - upleft - 1;
  33.                 } else if ((isClockwise && direction==Direction.LEFT) ||(!isClockwise && direction==Direction.UP)) {        // 顺时先向左或逆时先向上
  34.                         y = x = n - upleft - 1;
  35.                 }
  36.                 intArray[y][x] = num++;                // 把1放到固定的位置
  37.                 int moveCount = 1;                                // 控制一个方向要存几个数字
  38.                 for (int i=1; num<=max; i++) {
  39.                         for(int j=0; j<moveCount && num<=max; j++) {
  40.                                 switch(direction) {
  41.                                 case UP:          intArray[--y][x] = num++; break; // 上
  42.                                 case DOWN:         intArray[++y][x] = num++; break; // 下
  43.                                 case LEFT:         intArray[y][--x] = num++; break; // 左
  44.                                 case RIGHT:         intArray[y][++x] = num++; break; // 右
  45.                                 }
  46.                         }
  47.                         direction = direction.nextDirection(isClockwise); //上面的for循环结束后说明一个方向的数字存满了,得换一个方向继承存
  48.                         if(i%2 == 0) moveCount++;//i每循环两次count就自加1
  49.                 }
  50.                 return intArray;
  51.         }

  52.         /** 画自定义图形方法 */
  53.         public static void drawCustomShape(int[][] intArray) {
  54.                 int n = intArray!=null ? intArray.length : 3;
  55.                 int starWidth = n + 2;                 // 星星的宽度
  56.                 for (int i = 1; i <= starWidth; i++) {                 // 控制输出的行数
  57.                         for (int j = 1; j <= starWidth; j++) {         // 控制输出的列数
  58.                                 if (j == starWidth) /*如果是最后一列: */ System.out.print("*\n");        
  59.                                 else if ((i == 1 || i == starWidth || j == 1)) /* 如果是第一行、或最后一行、或第一列: */ System.out.print("*\t");       
  60.                                 else /* 其他情况 */ System.out.print(intArray == null ? "\t" : intArray[i-2][j-2] + "\t");
  61.                         }
  62.                 }
  63.         }
  64.        
  65.         enum Direction {
  66.                 UP, DOWN, LEFT, RIGHT;// 上、下、左、右
  67.                 public Direction nextDirection(boolean isClockwise) {// 参数isClockwise指示是否使用顺时针
  68.                         switch (this) {
  69.                         case UP:         return isClockwise?RIGHT:LEFT;        // 顺时针上移后右移,逆时针上移后左移
  70.                         case DOWN:  return isClockwise?LEFT:RIGHT;        // 顺时针下移后左移,逆时针下移后右移
  71.                         case LEFT:  return isClockwise?UP:DOWN;                // 顺时针左移后上移,逆时针左移后下移
  72.                         case RIGHT: return isClockwise?DOWN:UP;                // 顺时针右移后下移,逆时针右移后上移
  73.                         default:        return null;
  74.                         }
  75.                 }
  76.         }
复制代码

作者: 戴振良    时间: 2013-5-5 16:29
本帖最后由 戴振良 于 2013-5-5 16:59 编辑

看了一些大家的答案,发现不变的原理就一个,都是先找出数字1在数组中的位置,然后通过x、和y方向的加1或减1来移动坐标,顺序存放2、3、4、5。。。。
我想这也是目前为止大家都是用这个规律来完成答题的,这个是共同点,大家的不同点在于以怎样的方式让坐标右移、下移、左移、上移的方式不同。目前我发现了3种方式:
上面是一种方式
还有一种我在帖子有详细说明:http://bbs.itheima.com/thread-48459-1-1.html
还有一种是这样的(当然了可能还有很多种方式):
如图:从1的位置开始向右移动,右移1格放2,下移1格放3,总结如下:
1、  右移1格,下移1格 (右移即x轴++,下移即y轴++)
2、  左移2格,上移2格 (左移即x轴--,上移即y轴--)
3、  右移3格,下称3格
4、  左移4格,下移4格
分析规律:
l        这里的移动1、2、3、4格数可以用一个变量自增,如moveCount++
l        每个方向移动多少次可以用一个for循环完成,如for(int j=0;j<moveCount;j++)
l        通过上面4步的分析,可以知道右移、下移的格数是一样的,可用同一个循环;左移、上移的格数是一样的,可用同一个循环
l        还可以看到移动的次数是单数的话是右移和下移(单数即:moveCount%2 == 1)
l        移动的是格数是双数的话是左移和下移,(单数即:moveCount%2 == 0)
这些思路是从论坛里:shenqi代码里看出来的,他的代码非常简洁,估计是最少代码了,我再做了稍微修改,总共实现才30几行代码:
  1. /** 获取螺旋数字数组 */
  2.         private static String[][] getHeilxNumberArray(int n) {
  3.                 String[][] heilx = new String[n][n];
  4.                 int y = (n+1)/2-1;        // y轴
  5.                 int x = y;                        // x轴
  6.                 int moveCount = 1;        // 指示每个方向上要移动的次数
  7.                 int num = 1;                // 要存入的数字
  8.                 int max = n * n;        // 要存入的数字的最大值
  9.                 heilx[y][x] = num++ + "\t";
  10.                 while (num <= max) {
  11.                         for (int i = 0; i<moveCount && num<=max; i++) {
  12.                                 if (moveCount % 2 == 0) heilx[y][--x] = num++ + "\t";        // moveCount是双数时x坐标减1
  13.                                 else heilx[y][++x] = num++ + "\t";                                                // moveCount是单数是x坐标加1
  14.                         }

  15.                         for (int i = 0; i<moveCount && num<=max; i++) {
  16.                                 if (moveCount % 2 == 0) heilx[--y][x] = num++ + "\t";        // moveCount是双数时y坐标减1
  17.                                 else heilx[++y][x] = num++ + "\t";                                                // moveCount是单数是y坐标加1
  18.                         }
  19.                         moveCount++;
  20.                 }
  21.                 return heilx;
  22.         }
  23.         
  24.         public static void main(String[] args) {
  25.                 int n = 3;
  26.                 String[][] heilx = getHeilxNumberArray(n);
  27.                 for (int y = 1,len = n+2; y <= len; y++) {// 遍历数组
  28.                         for (int x = 1; x <= len; x++) {
  29.                                 if (x == len) System.out.print("*\t\n");// 凡是最后一列都是*号和换行
  30.                                 else if (y == 1 || y == len || x==1) System.out.print("*\t");// 凡是第一行和最后一行和第一列都是*号
  31.                                 else System.out.print(heilx[y-2][x-2]);        //其他情况都是数字
  32.                         }
  33.                 }
  34.         }
复制代码

作者: 黄茂霖    时间: 2013-5-5 17:29
顶下!~!~
作者: 芦玉明    时间: 2013-5-5 20:23
感谢楼主,必须顶起
作者: lipingan0520    时间: 2013-5-5 21:07
{:soso_e100:}分析的好详细啊。楼主有心了
作者: 戴振良    时间: 2013-5-5 21:33
lipingan0520 发表于 2013-5-5 21:07
分析的好详细啊。楼主有心了

我也是受益于别人,也应该让其他人受益
作者: 黄玉昆    时间: 2013-5-6 08:25
没想到我的一道题,能让楼主这么用心,我真心的自愧不如了。楼主辛苦了
作者: 戴振良    时间: 2013-5-6 10:39
黄玉昆 发表于 2013-5-6 08:25
没想到我的一道题,能让楼主这么用心,我真心的自愧不如了。楼主辛苦了

哪的事呀,我自己做出来了自己也开心啊!
作者: 曹睿翔    时间: 2013-5-6 21:59
非常值得学习!




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2