题目:输出这样的数组:
03 05 08 14 16
04 09 13 17 22
10 12 18 21 23
11 19 20 24 25
当然你的问题不应该是一位数输出 “1”还是“01”。
这是一类型问题,把数字按照一定的顺序(或者图案、路径)输出,我们姑且称之为“PrintArray”问题吧。
--------------------------------------------------------------------------
遇到这个问题的时候,我们的大部分人会想着怎样在print的时候找到对应的某个位置是那个数,这样的方法也不是说就不行,反正我实在是懒得伤脑筋。那天遇到这个问题的时候,我瞬间想到的是Iterator,熟悉Java集合框架或者STL的朋友肯定对此有所了解:迭代模式。
我们把上面的数看成一个集合,这个集合的元素当然也是需要包装一下的,因为我们在知道元素的值的同时,还想知道它究竟对应于输出时的位置(x,y)。从程序设计的角度讲,元素的迭代方式(即顺序或者路径等等)应该由迭代器决定,元素基本上只是一个pojo,不过我这里只做了简单的实现,并没有把二者分开。
让我们回想一下,STL当中定义的迭代器的类型共有四种,分别是random(随机访问,比如数组,访问时间O(1)),bidirect(双向),forward(单向),input。对于本题来说,bidirect是比较容易实现的,算法也是O(1),而random的话,实际上可以通过执行i次bidirect从形式上实现,但复杂度就成为O(n)了,所以我们只考虑bidirect的情况。
对于bidirect来说,forward和backward实际上经常是逆操作,我下面列出的代码只给出forward,backward的代码看大家自己的兴趣了。
class Iter {
boolean direction = true;
int cur = 1;
int x = 0;
int y = 0;
int N;
int sqrN;
public Iter(int N) {
this.N = N;
this.sqrN = (int) Math.sqrt((double) N);
if (sqrN * sqrN != N) {
throw new RuntimeException();
}
}
public boolean forward() {
if (cur == N) {
return false;
}
if (direction) {
if (x == sqrN - 1) {
y += 1;
direction = false;
} else if (y == 0) {
x += 1;
direction = false;
} else {
x += 1;
y -= 1;
}
} else {
if (y == sqrN - 1) {
x += 1;
direction = true;
} else if (x == 0) {
y += 1;
direction = true;
} else {
x -= 1;
y += 1;
}
}
cur++;
return true;
}
}
这里的N是输出的最大值,我们要求它是一个完全平方数,当N不符合要求时,抛异常。
代码中的direction可并不是指输出的方向,而是数字在输出时,按照数字顺序输出时呈现在路径上的顺序:从左下角到右上角,或是从右上角到左下角。
这时候有人说我们是要输出这个数组啊,没问题,下面给出main的代码:
public class PrintArray {
public static void main(String[] args) {
Iter it = new Iter(25);
int[][] array = new int[it.sqrN][it.sqrN];
array[0][0] = it.cur;
while (it.forward()) {
array[it.y][it.x] = it.cur;
// System.out.println(it.cur + "--(" + it.x + "," + it.y + ")");
}
for (int i = 0; i < it.sqrN; i++) {
for (int j = 0; j < it.sqrN; j++) {
System.out.print(String.format("%02d", array[i][j]) + "\t");
}
System.out.println();
}
}
}
我们先通过迭代把所有需要输出的数存入一个sqrtN阶的数组中,然后依次打印就行了。你可以把N取得很大,比如我取它为64,那么结果就像这样:
01 02 06 07 15 16 28 29
03 05 08 14 17 27 30 43
04 09 13 18 26 31 42 44
10 12 19 25 32 41 45 54
11 20 24 33 40 46 53 55
21 23 34 39 47 52 56 61
22 35 38 48 51 57 60 62
36 37 49 50 58 59 63 64
---------------------------------------------------------------------------------------
这个思路实际上可以用于很多数组输出的问题,比如:
01 28 27 26 25 24 23 22
02 29 48 47 46 45 44 21
03 30 49 60 59 58 43 20
04 31 50 61 64 57 42 19
05 32 51 62 63 56 41 18
06 33 52 53 54 55 40 17
07 34 35 36 37 38 39 16
08 09 10 11 12 13 14 15
逆时针旋转的形状输出,那么你只需要修改一下Iter即可。
class Iter2 {
enum Direction {
UP, DOWN, LEFT, RIGHT
}
Direction direction = Direction.DOWN;
int cur = 1;
int x = 0;
int y = 0;
int N;
int sqrN;
int maxX;
int maxY;
int minX;
int minY;
public Iter2(int N) {
this.N = N;
this.sqrN = (int) Math.sqrt((double) N);
if (sqrN * sqrN != N) {
throw new RuntimeException();
}
maxX = sqrN - 1;
maxY = sqrN - 1;
minX = 0;
minY = 0;
}
public boolean forward() {
if (cur == N) {
return false;
}
switch (direction) {
case DOWN:
if (++y == maxY) {
direction = Direction.RIGHT;
++minX;
} else {
}
break;
case LEFT:
if (--x == minX) {
direction = Direction.DOWN;
++minY;
}
break;
case RIGHT:
if (++x == maxX) {
direction = Direction.UP;
--maxY;
}
break;
case UP:
if (--y == minY) {
direction = Direction.LEFT;
--maxX;
}
break;
default:
break;
}
cur++;
return true;
}
}
那么实际上,既然知道了iter可以按照要求输出这些数字,就可以给它配备算法。这种做法有点儿类似STL的算法设计模式。
通过简单的修改forward的代码就可以让数字按照各式各样的图案输出。为了增加趣味,我们让上面的逆时针输出点儿别的: