打印直角三角形、等腰三角形、菱形
(1) 西南方向的直角三角形
我们肯定知道打印3行3列的矩形怎么做,就是外循环 i 从1到3,内循环 j 从1到3。
但是很明显,如果我们想要打印西南方向的直角三角形,每次内循环 j 的范围是不一样的。问题来了:
我们如何控制它在第一行的时候,在 j = 1打印完星星后停止;
在第一行的时候,在 j = 2打印完星星后停止;在 j = 3打印完星星后停止?
如果有一个停止指针能在执行第一次外循环的时候指向 1,在执行第二次外循环的时候指向 2, 在执行第三次外循环的时候指向 3就好了!
为了便于观察 i 和 j 的关系,将 i 和 j 放到同一水平去画,如下图:
看上图,我们会发现,i 所在的位置,正好是我们需要的《停止指针》的位置。于是就能写出代码,注意一下,每一行结束的时候需要回车,也就是说内循环结束之后循环打印回车。
for(int i=1; i<=3; i++){
for(int j=1; j<=i; j++){
System.out.print("*");
}
System.out.println();
}
//运行结果
*
**
***
(2) 西北方向的直角三角形
对于西北方向的直角三角形,看上图,我们发现 i 的位置并不是我们想要的《停止打印星星的位置》
那么问题来了,《停止打印星星的位置》(我们称为停止指针指向的位置)是在哪呢?
虽然此时我们不能将 i 作为《停止指针》,但是我们可以找找我们需要的《停止指针》跟 i 的关系。我们知道 i 分别是 1、2、3,而我们需要的《停止指针》的位置分别是 3、2、1,所以我们可以把 4 - i 作为《停止指针》。
(实际上没有停止指针这种说法,我只是为了便于理解打得一个比喻)
for(int i=1; i<=3; i++){
for(int j=1; j<=4-i; j++){
System.out.print("*");
}
System.out.println();
}
//运行结果
***
**
*
那么其他人也是这样想的吗?那倒未必!因为我们发现,上面我们的分析中一直都有一个前提假设,我们假设了 j 表示列标,什么意思呢?意思是:我们在打印第1行第1个星星的时候,是不是满足了 i = 1,j = 1?
但实际上呢,我们打印这个直角三角形并没有说 i 必须表示行号,j 必须表示列号,难道说 j = 2的时候我就必须得打印第2列的东西吗?事实上!无论i 或者 j 等于多少,他打印的顺序都是从左到右一个个来。
所以!实际上我们只需要控制第一次外循环打印3次星星,第二次外循环打印2次星星,第三次外循环打印1次星星。此时,问题就变成了:如何控制在 i = 1的时候内循环执行三次打印星星,在 i = 2的时候内循环执行两次打印星星,在 i = 3 的时候内循环执行1次打印星星?
我们知道,控制内循环执行次数是取决于 j 的范围,那么我们如何让 j 的范围 在 每次外循环不同的时候不同呢? 其实就是像上面那样,借助 i 。我们知道 i 分别是 1、2、3,那我们的 j 一开始应该在 i 的右边还是左边呢,明显我们最终的目标是 分别循环 3、2、1,所以 j 应该是在 i 的右边,这样的话 i 在增加的时候,i 和 j的距离就会慢慢减少。那么 j 应该 定为多少呢?
看下图就明白, j 应该定为 3。
可能你觉得废话很多,但是其实关键就一句话,《关注 i 和 j 的距离,它们的距离决定了此次外循环的内循环执行次数》
for(int i=1; i<=3; i++){
for(int j=i; j<=3; j++){ //或者 for(int j=3; j>=i; j--)
System.out.print("*");
}
System.out.println();
}
//运行结果
***
**
*
(3) 东南方向的直角三角形
方法1:
我们知道,打印是从左到右的。很显然,前面两种情况星星在空格前面,我们是不用去管空格的。但是现在,空格在星星前面,我们必须得打印空格,然后再打印星星。
根据前面的经验,我们发现这些问题的关键点无非就是《找空格和星星的分界点》。
从上图可以看出,分界点就是在 4 - i 的位置。那我们只需要让 j 从1到3扫描,在扫描到 i 的位置之前打印空格,扫描到 i 的位置之后打印星星。
for(int i=1; i<=3; i++){
for(int j=1; j<=3; j++){
if(j < 4 - i){
System.out.print(" ");
}else{
System.out.print("*");
}
}
System.out.println();
}
*
**
***
方法2:
我们是让j从1到3扫描,也就是说整行都扫描了,然后每一列需要打印什么,再分情况讨论,分情况的方法是设置《停止指针》。
那么我们可不可以先每一行前面的空格都打印好,再去打印每一行的星星呢?
那么如何把每一行前面的空格都打印好呢?前面我们说那个《关注 i 和 j 的距离》就发挥了作用,其实我们想第一行打印2个空格,第二行打印1个空格,第三行打印0个空格,无非不就是《控制 j 的范围》,控制 j 的范围无非就是要《关注 i 和 j 的距离》。
看下图。明显这次要设置 j 从i到2。
打印完每一行的空格之后,就要打印每一行的星星。
第一行打印1个星星,第二行打印2个星星,第三行3个星星。
所以控制 i 和 j 的距离分别是 1,2,3就行啦。那 j 的范围就是 1到 i 。
for(int i=1; i<=3; i++){
for(int j=i; j<=2; j++){
System.out.print(" ");
}
for(int j=1; j<=i; j++){
System.out.print("*");
}
System.out.println();
}
*
**
***
方法3:
前面的思路都是 j 从左到右扫描,如果j 从右到左扫描呢?
然后你就发现,在方法1中 j 从左到右扫描,分界点是在 4-i 的位置(1到 3-i 要打印空格,之后要打印星星)。
如果 j 从右到左扫描,分界点是在 i 的位置(在 3 到 i+1 要打印空格,然后就是打印星星),看下图就明白,红色表示打印每一行的空格数分别是2, 1,0,黑色表示打印每一行的星星数分别是1, 2, 3, j是从3到1扫描。
显然方法3跟方法1的核心思想都是《找到一个分界点》,以及《关注 i 和 j 的距离》,两种方法互为相反的思路,但是最终的目的都是跟《i 和 j 的距离》有关。
事实上!我认为方法1才是最简单的思路,因为方法1中的 j 是表示列号的,而方法2和方法3的 j 都不是表示 列号!但是我为什么要把各种思路想一想,因为各种思路都想过以后,发现他们都是如出一辙!不管他们怎么弄,他们都是要控制第一行打印2个空格,然后打印1个星星;第二行打印1个空格,然后打印两个星星.;第三行打印0个空格,然后打印3个星星。
for(int i=1; i<=3; i++){
for(int j=3; j>=1; j--){
if(j > i){
System.out.print(" ");
}else{
System.out.print("*");
}
}
System.out.println();
}
*
**
***
(4) 东北方向的直角三角形
有了前三个的经验,我们来整理一下思路。
首先,一看他前面是空格后面是星星,说明空格和星星是要分别处理的。
方法一是 j 从1到3扫描,然后分界点之前打印空格,分界点之后打印星星,分界点明显是 i 的位置。
for(int i=1; i<=3; i++){
for(int j=1; j<=3; j++){
if(j < i){
System.out.print(" ");
}else{
System.out.print("*");
}
}
System.out.println();
***
**
*
方法二是先把每一行的空格打印出来,把每一行的空格数都看一下,分别是 0,1, 2,这个距离明显是在增加。
我们知道 i 分别是 1,2, 3,要让 i 和 j 的距离增加,明显是要把 j 放在左边,然后想想,
当 i = 1的时候,j 在什么位置,i 和 j 的距离为0?肯定是越过 i 的位置,j 要放在左边,j 又要越过 i,推理出 j = 2
当 i = 2的时候,j 在什么位置,i 和 j 的距离为1?肯定是跟 i 重合的位置,推理出 j = 2
当 i = 3的时候,j 在什么位置,i 和 j 的距离为2?推理出 j = 2。
上面三种推理方式,无论选哪种都能推理出 j = 2,建议选第二种推理方式。
所以 j 的范围是 2 到 i。
(东南方向的直角三角形我们分析出是 i 到 2,如果你看不懂为什么这是 2到 i,可见只是反了过来,可以去看看东南方向的直角三角形那张 x 轴的图)
再看看每一行的星星数,分别是 3,2, 1。 距离在减少,i 分别是 1, 2,3,既然 i 在向x轴的正方向增加,想让 i 和 j 的距离减少,那 j 必然是站在 i 的右边。
当 i = 3的是,j 在什么位置, i 和 j 的距离为1?明显是 j = 3的时候。
所以 j 的范围是 i 到 3
最后,别忘了每一行结束要打印回车。
for(int i=1; i<=3; i++){
for(int j=i; j<=1; j++){
System.out.print(" ");
}
for(int j=3; j>=i; j--){
System.out.print("*");
}
System.out.println();
}
***
**
*
(5) 内部没有空格的等腰三角形
经过前面一波猛如虎的分析,突然就发现管它是什么形状,都是一样的分析方法。
第一,空格前面有星星,那肯定要先打印空格,再打印星星。
第二,观察星星前面的每一行空格是多少,分别是2,1,0,
距离在减少,由于i分别是1, 2,3在增加,所以 j 必定在 i 的右边。再看距离为1的时候 i =2,所以 j的范围肯定是 i 到 2。
(看下图就明白我在说啥,注意,我所说的距离是包括首尾的哈,比如我说 i = 1和 j =2的距离是2,意思是包括1和2。比如 i 和 j都是2的时候,距离是1,因为要包括2本身。其实严格来说不能称为《距离》,不过反正你懂什么我说的这个距离什么意思就行)
第三,观察每一行几个星星,分别是1, 3, 5,明显都是奇数,所以 j 的范围是 1到2*i - 1。
我们为什么不去分析 i 和 j 的距离? 因为此次的距离不是逐渐加1或者减1变化的,跟 i 的步长不一致,如果要分析的话,如下图所示,j 应该分别是1,4,7,等差数列公式 1+(i-1)3 = 3 i - 2。分析出 j 的范围是 i 到 3*i-2。你还真别说,也可以这样分析。
这启示我们,控制这个距离为1, 3,5这种步长跟 i 的步长不一致的情况,应该直接去看 1,3,5本身跟 i 有什么联系,能否将他们表达成跟 i 有关的表达式,然后从1开始,就能产生1,3,5这样不同的距离。
for(int i=1; i<=3; i++){
for(int j=i; j<=2; j++){
System.out.print(" ");
}
for(int j=1; j<=2*i-1; j++){
System.out.print("*");
}
System.out.println();
}
for(int i=1; i<=3; i++){
for(int j=i; j<=2; j++){
System.out.print(" ");
}
for(int j=i; j<=3*i-2; j++){
System.out.print("*");
}
System.out.println();
}
*
***
*****
(6) 内部没有空格的倒立的等腰三角形
先打前面的空格再星星。
前面的空格数分别是0, 1,2,所以 j 的范围是 2 到 i
星星数分别是 5, 3,1,所以 j 的范围是 1 到 5+(i-1)*(-2)(等差数列公式)即 1 到 -2i+7
for(int i=1; i<=3; i++){
for(int j=2; j<=i; j++){
System.out.print(" ");
}
for(int j=1; j<=-2*i+7; j++){
System.out.print("*");
}
System.out.println();
}
*****
***
*
(7) 内部有空格的等腰三角形
可以把星星和后面的一个空格看做整体,然后就直接转换成了东南方向的直角三角形的问题。
for(int i=1; i<=3; i++){
for(int j=1; j<=3; j++){
if(j < 4 - i){
System.out.print(" ");
}else{
System.out.print("* ");
}
}
System.out.println();
}
*
* *
* * *
(8) 内部有空格的倒立的等腰三角形
其实就是东北方向直角三角形的问题
一看前面的空格数,0,1,2 所以 j 的范围 2 到 i
星星数 3, 2,1 所以 j 的范围是 i 到 3 或者说 3 到 i
for(int i=1; i<=3; i++){
for(int j=2; j<=i; j++){
System.out.print(" ");
}
for(int j=i; j<=3; j++){ //for(int j=3; j>=i; j--)
System.out.print("* ");
}
System.out.println();
}
* * *
* *
*
(9) 菱形
我们把菱形切割一下,就转换成了打印两个等腰三角形的问题,一个是正立的,一个是倒立的。
第一个是正立的等腰三角形:
i 是 1 到 3
空格数 2, 1,0 ,所以 j 的范围是 i 到 2
星星数 1, 3, 5, 所以 j 的范围是 1 到 2i-1
第二个是倒立的等腰三角形:
i 是 1 到 2
空格数 1, 2, 所以 j 的范围是 1到 i
星星数 3,1,所以 j 的范围是 1 到 3+(i-1)*(-2) ,即 1 到 -2i+5
for(int i=1; i<=3; i++){
for(int j=i; j<=2; j++){
System.out.print(" ");
}
for(int j=1; j<=2*i-1; j++){
System.out.print("*");
}
System.out.println();
}
for(int i=1;i<=2; i++){
for(int j=1; j<=i; j++){
System.out.print(" ");
}
for(int j=1; j<=-2*i+5; j++){
System.out.print("*");
}
System.out.println();
}
*
***
*****
***
*
可不可以不切割呢?不切割的话行数是1到5,左边的空格分别是2 ,1, 0, 1, 2,恐怕很难找到 j 跟 i的关系,可能很难处理,所以还是切割吧。
总结
问题的关键在于在每次外循环执行时控制 j 的范围,这个范围的距离将直接决定了内循环执行几次(或说打印几次),所谓范围的意思无非不就是 有个最小值和最大值,那么这个最小值和最大值的设定你可以有两个选择:要么固定点,要么是关于i的表达式。但无论你怎么做,目的只有一个,要让最大值和最小值的距离能够按照你希望的那样。就上面的例子具体而言,当你所需的距离正好是 i 的步长的时候,那么j的范围其中一端肯定是i,另一端你就找一个固定点;当你所需的距离不是i的步长,但是跟i有关系,那你就找到那个跟i的关系。