黑马程序员技术交流社区
标题: 螺旋数字挑战题提升级版 [打印本页]
作者: 戴振良 时间: 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是放在坐标1、1的位置,完成螺旋状的步骤如下:
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种变化,它的那个枚举用的实在是太漂亮了!- public static void main(String[] args) {
- int n = 2;
- int[][] intArray = fillIntArray(n, false, Direction.RIGHT);//获取螺旋状的int数组
- drawCustomShape(intArray);
- //System.out.println("\n=========================================\n");
- //drawCustomShape(null); //打印一个空框
- }
- /**
- * 填充螺旋整形数组的方法
- * @param n 指示数据的长度
- * @param isClockwise 指示是否顺时针显示
- * @param direction 指示先向哪个方向移动
- * @return 螺旋整形数组
- */
- public static int[][] fillIntArray(int n, boolean isClockwise, Direction direction) {
- if (n <= 0) throw new IllegalArgumentException("参数n必须是大于或等于1的整数");
- int[][] intArray = new int[n][n];
- int max = n * n; // 最大输出数,可用来做结束循环的条件
- int num = 1; // 当前要输出的数,默认从1开始
- int x; // 当前要输出的数字在X轴的位置
- int y; // 当前要输出的数字在Y轴的位置
-
- //根据各种情况初始化数字1在数组中的x、y坐标
- int upleft = x = y = (n + 1) / 2 - 1; //顺时针先向右、或逆时针先向下 的情况下 1的坐标相对左上角对称
- if(n%2==1) {
- // n是单数的情况下,数字1在整个图形的正中间,算法跟顺时针先向右一样
- } else if ((isClockwise && direction==Direction.UP) ||(!isClockwise && direction==Direction.RIGHT)) { // 顺时先向上或逆时先向右
- y = n - upleft - 1;
- x = n - upleft - 2;
- } else if ((isClockwise && direction==Direction.DOWN) ||(!isClockwise && direction==Direction.LEFT)) { // 顺时先向下或逆时先向右
- y = n - upleft - 2;
- x = n - upleft - 1;
- } else if ((isClockwise && direction==Direction.LEFT) ||(!isClockwise && direction==Direction.UP)) { // 顺时先向左或逆时先向上
- y = x = n - upleft - 1;
- }
- intArray[y][x] = num++; // 把1放到固定的位置
- int moveCount = 1; // 控制一个方向要存几个数字
- for (int i=1; num<=max; i++) {
- for(int j=0; j<moveCount && num<=max; j++) {
- switch(direction) {
- case UP: intArray[--y][x] = num++; break; // 上
- case DOWN: intArray[++y][x] = num++; break; // 下
- case LEFT: intArray[y][--x] = num++; break; // 左
- case RIGHT: intArray[y][++x] = num++; break; // 右
- }
- }
- direction = direction.nextDirection(isClockwise); //上面的for循环结束后说明一个方向的数字存满了,得换一个方向继承存
- if(i%2 == 0) moveCount++;//i每循环两次count就自加1
- }
- return intArray;
- }
- /** 画自定义图形方法 */
- public static void drawCustomShape(int[][] intArray) {
- int n = intArray!=null ? intArray.length : 3;
- int starWidth = n + 2; // 星星的宽度
- for (int i = 1; i <= starWidth; i++) { // 控制输出的行数
- for (int j = 1; j <= starWidth; j++) { // 控制输出的列数
- if (j == starWidth) /*如果是最后一列: */ System.out.print("*\n");
- else if ((i == 1 || i == starWidth || j == 1)) /* 如果是第一行、或最后一行、或第一列: */ System.out.print("*\t");
- else /* 其他情况 */ System.out.print(intArray == null ? "\t" : intArray[i-2][j-2] + "\t");
- }
- }
- }
-
- enum Direction {
- UP, DOWN, LEFT, RIGHT;// 上、下、左、右
- public Direction nextDirection(boolean isClockwise) {// 参数isClockwise指示是否使用顺时针
- switch (this) {
- case UP: return isClockwise?RIGHT:LEFT; // 顺时针上移后右移,逆时针上移后左移
- case DOWN: return isClockwise?LEFT:RIGHT; // 顺时针下移后左移,逆时针下移后右移
- case LEFT: return isClockwise?UP:DOWN; // 顺时针左移后上移,逆时针左移后下移
- case RIGHT: return isClockwise?DOWN:UP; // 顺时针右移后下移,逆时针右移后上移
- default: return null;
- }
- }
- }
复制代码
作者: 戴振良 时间: 2013-5-5 16:29
本帖最后由 戴振良 于 2013-5-5 16:59 编辑
看了一些大家的答案,发现不变的原理就一个,都是先找出数字1在数组中的位置,然后通过x、和y方向的加1或减1来移动坐标,顺序存放2、3、4、5。。。。
我想这也是目前为止大家都是用这个规律来完成答题的,这个是共同点,大家的不同点在于以怎样的方式让坐标右移、下移、左移、上移的方式不同。目前我发现了3种方式:
上面是一种方式
还有一种是这样的(当然了可能还有很多种方式):
如图:从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几行代码:- /** 获取螺旋数字数组 */
- private static String[][] getHeilxNumberArray(int n) {
- String[][] heilx = new String[n][n];
- int y = (n+1)/2-1; // y轴
- int x = y; // x轴
- int moveCount = 1; // 指示每个方向上要移动的次数
- int num = 1; // 要存入的数字
- int max = n * n; // 要存入的数字的最大值
- heilx[y][x] = num++ + "\t";
- while (num <= max) {
- for (int i = 0; i<moveCount && num<=max; i++) {
- if (moveCount % 2 == 0) heilx[y][--x] = num++ + "\t"; // moveCount是双数时x坐标减1
- else heilx[y][++x] = num++ + "\t"; // moveCount是单数是x坐标加1
- }
- for (int i = 0; i<moveCount && num<=max; i++) {
- if (moveCount % 2 == 0) heilx[--y][x] = num++ + "\t"; // moveCount是双数时y坐标减1
- else heilx[++y][x] = num++ + "\t"; // moveCount是单数是y坐标加1
- }
- moveCount++;
- }
- return heilx;
- }
-
- public static void main(String[] args) {
- int n = 3;
- String[][] heilx = getHeilxNumberArray(n);
- for (int y = 1,len = n+2; y <= len; y++) {// 遍历数组
- for (int x = 1; x <= len; x++) {
- if (x == len) System.out.print("*\t\n");// 凡是最后一列都是*号和换行
- else if (y == 1 || y == len || x==1) System.out.print("*\t");// 凡是第一行和最后一行和第一列都是*号
- else System.out.print(heilx[y-2][x-2]); //其他情况都是数字
- }
- }
- }
复制代码
作者: 黄茂霖 时间: 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 |