这里主要介绍杨辉三角的三个打印方法,由浅入深,建议顺序观看
1. 基础版方法
先来看杨辉三角长什么样:
正如图片中所看到的,杨辉 三角就长这个样子,杨辉三角具有很多特性,但是在这里我们只使用他的其中 一个特性,即:
每个数等于它上方两数之和。
在这里我们先将杨辉三角斜过来看,简化操作,通过将杨辉三角斜过来看可以得到一个这样的直角三角形:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
通过简化后的杨辉三角我们来考虑每一行每个数如何生成,
假设我们要 求第 i 行,第 j列的数值,根据杨辉三角中每个数等于它上方两数之和,我们可以得到第 i 行,第 j列数字的表达式为
[ i ] [ j ] = [ i-1 ] [ j ] + [ i -1 ][ j -1 ]
但是观察我们简化的杨辉三角,可以发现有特殊情况,就是可以看到第一列的数值全部为1,而且第一行第一列,第二行第二列,第三行第三列等等都是1,转化为我们的代码就是:
if (j = 1 || i = j){ return 1 }
根据以上分析将这些特性转化成我们的代码:
public static int findValue(int i, int j){
if (j == 1 || i == j){
return 1;
}
return findValue(i-1,j) + findValue(i-1,j-1);
}
经过测试,他可以正确的找到我们第 i行, j列的值。
接下来我们利用for循环,循环的打印出来我们简化后的杨辉三角,代码如下:
public static void print (int n){
//外层循环打印行
for (int i =1; i <= n; i++){
//内层循环打印列
for (int j = 1; j <= i; j++){
//将数字按照定长输出
System.out.printf("%-2d",findValue(array,i,j));
}
System.out.println();
}
}
执行输出结果:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
代码运行正确
接下来就要打印正常的杨辉三角,打印正常的杨辉三角需要在每行打印部分空格,使其从 “直角三角形” 变成 “等腰三角形”
仔细观察杨辉三角不难发现每一列数字两边的空格都与行数有关,我们很容易可以的出数字前需要打印的空格数量:假设这是一个n行n列的杨辉三角,行数用 i表示,很容易得出规律:
打印空格数量 = n - i;
所以到这里我们可以完整的打印出来我们的杨辉三角
代码示例
public class SanJiao {
//找到i行j列的值得方法
public static int findValue(int i, int j){
if (j == 1 || i == j){
return 1;
}
return findValue(i-1,j) + findValue(i-1,j-1);
}
//打印空格的方法
public static void printSpace(int n,int i){
//每一行需要打印空格的数量
int num = n-i;
for (int j = 1; j<=num; j++){
System.out.print(" ");
}
}
public static void print (int n){
//外层循环打印行
for (int i =1; i <= n; i++){
//每次打印列之前先将行空格进行打印
printSpace(n,i);
//内层循环打印列
for (int j = 1; j <= i; j++){
//将数字按照定长输出
System.out.printf("%-2d",findValue(array,i,j));
}
System.out.println();
}
}
public static void main(String[] args) {
print(5);
}
}
输出结果
2.升级版方法(记忆法优化)
为什么要升级我们的算法,我们知道,现在来检验一个算法的好坏通常是时间复杂度来判断,简单来说就是同样一个问题,谁的算法处理时间短,谁的算法就是好的算法。
对于基础版打印杨辉三角来说,他的时间复杂度已经达到了指数级,对于相对复杂的杨辉三角打印耗费的时间非常长,所有在这里在基础版方法的基础上对其进行优化。
通过杨辉三角的特性,我们可以了解到,杨辉三角每一行列的值都来自于上一行已经存在的值。
所以所谓记忆法,就是将杨辉三角每行列的值计算出来以后直接存储,那么对于我们要求的值不需要再一次的递归计算,而是直接使用已经存在的值即可,通过这一优化杨辉三角的时间复杂度大大降低,算法得到了优化。
既然存储杨辉三角每行列的值,我们很容易想到用二维数组来存储是一个很好的选择,所以我们将每行列的值计算出来将其存储到二维数组中,并做一判断,当要寻找的值在二维数组中存在时,直接返回数值,代码如下:
public class SanJiao {
//找到i行j列的值得方法
public static int findValue(int array[][],int i, int j){
//当二维数组中存在要找的值,直接返回这个值
if (array[i][j] > 0){
return array[i][j];
}
if (j == 1 || i == j){
array[i][j] =1;
return 1;
}
//计算结果存入二维数组
array[i][j] = findValue(array,i-1,j) + findValue(array,i-1,j-1);
return array[i][j];
}
//打印空格的方法
public static void printSpace(int n,int i){
//每一行需要打印空格的数量
int num = n-i;
for (int j = 1; j<=num; j++){
System.out.print(" ");
}
}
public static void print (int n){
//定义一个二维数组
//由于我们的杨辉三角从1行1列开始的,所以定义数组应定义n+1行n+1列(因为数组下标从0开始)
int array[][] = new int[n+1][n+1];
//外层循环打印行
for (int i =1; i <= n; i++){
//每次打印列之前先将行空格进行打印
printSpace(n,i);
//内层循环打印列
for (int j = 1; j <= i; j++){
//将数字按照定长输出
System.out.printf("%-2d",findValue(array,i,j));
}
System.out.println();
}
}
public static void main(String[] args) {
print(5);
}
}
输出结果
** 需要注意的是二维数组的初始长度,因为我是从1行1列开始打印的杨辉三角,所以定义的二维数组就要长度加一,原因是因为数组的下标从0行0列开始存取数据,当然,你也可以从0行0列开始打印杨辉三角,这样定义的数组不会产生空间浪费。
相比于基础版:
优点: 时间复杂度大大降低
缺点: 空间成本升高,空间复杂度升高
3.加强版方法(对空间复杂度的优化)
对于空间复杂度的优化,很容易想到 空间复用 的思想,由于我们生成行的数据只与它的上一行数据有关,而对于更早之前的数据其实是没有存在的必要的,那么从这一点出发来优化我们的代码。
解决思路:使用一维数组代替二维数组,每一行数据的生成就会覆盖原来的数据,以5行的杨辉三角为例,因为我们从一行一列开始打印三角,所以我们可以创建一个大小为6的数组来进行存储数据。
数组结构
0 0 0 0 0 0 数组初始状态
0 1 0 0 0 0 打印第一行数据
0 1 1 0 0 0 打印第二行数据
0 1 2 1 0 0 打印第三行数据
0 1 3 3 1 0 打印第四行数据
0 1 4 6 4 1 打印第五行数据
从这里我们很容易的找到规律,就是当前下标的数据是上一次这个下标存储的数据和前一下标的数据之和。比如对于第五行的下标2的数据4,他的数值来自于第四行的下标1和下标2的数据之和也就是1+3 = 4
假设数组为array 所以可得:
array [i] = array[i]+array[i-1]
利用这个公式,可以得出加强版的杨辉三角打印代码:
public class SanJiao {
//寻找值得方法,传入数组,以及行数
public static void findValue(int array[],int i){
//如果行数为1直接赋值
if (i == 1){
array[1] =1;
return;
}
//array[3] = array[3] + array[2]
//array[2] = array[2] + array[1]
for (int j=i;j>0;j--){
array[j] = array[j] + array[j-1];
}
}
//打印空格的方法
public static void printSpace(int n,int i){
//每一行需要打印空格的数量
int num = n-i;
for (int j = 1; j<=num; j++){
System.out.print(" ");
}
}
public static void print (int n){
//定义一个二维数组
//由于我们的杨辉三角从1行1列开始的,所以定义数组应定义n+1行n+1列(因为数组下标从0开始)
int array[] = new int[n+1];
//外层循环打印行
for (int i =1; i <= n; i++){
findValue(array,i);
//每次打印列之前先将行空格进行打印
printSpace(n,i);
//将数字按照定长输出
for (int j = 1;j <= i; j++){
System.out.printf("%-2d",array[j]);
}
System.out.println();
}
}
public static void main(String[] args) {
print(5);
}
}
输出结果
从代码中可以注意到我们并没有使用递归去实现杨辉三角的打印,而是使用循环调用的方法来实现最终的结果,在这段代码中由于一维空间的循环复用,使得我们的代码在空间复杂度上大大降低,优化了我们的算法.
总结
从三种不同的算法中我们可以看到对于同一个问题,我们可以衍生出来许许多多的方法来解决问题。
基于不同的角度可以有很多的算法。
而对于一个算法,我们可以基于时间和空间复杂度上对算法进行优化考虑,从而得到更好的算法。