转载自:http://blog.csdn.net/ylyg050518/article/details/48517151
今天这道题目是也是一个经典的问题,打印Pascal’s Triangle,(帕斯卡三角或者说是杨辉三角)。
问题描述
Given numRows, generate the first numRows of Pascal’s triangle.
For example, given numRows = 5,
Return
[
[1],
[1,1],
[1,2,1],
[1,3,3,1],
[1,4,6,4,1]
]
大意: 给定行数numRows,生成帕斯卡三角的前numRows行,例如,给定numRows=5,返回
[
[1],
[1,1],
[1,2,1],
[1,3,3,1],
[1,4,6,4,1]
]
思路分析
其实只要你能手动写出帕斯卡三角,你就已经掌握了具体的实现原理,剩下的就是如何用代码描述它而已。
我们总结出帕斯卡三角的几个规律:
1. 第n行,共有n个元素。
2. 每一行的边界元素都是1。
3. 从第三行开始,除了边界元素外,所有的元素值都是在上一行元素中位于当前元素双肩位置的元素之和。
题目要求我们返回帕斯卡三角的集合,我们先不考虑结果存储的代码实现细节,我们先采用打印的方式来控制台直接输出结果,只要能够成功输出,后期我们只要照猫画虎,采用合适的存储方式来生成结果就行了。
存储方法:如果仅从打印输出的角度来看,很容易想到的是,帕斯卡三角的元素存储方式可以使用二维数组。由于Java语言的特性,我们可以使用不规则的动态二维数组来保存帕斯卡三角的结果。这里,我们定义一个整型二维数组triangle[numRows][],采用行优先的方式存储,数组第一维存储帕斯卡三角的行数numRows,第二维长度根据当前行的长度动态生成。
实现细节:使用双层循环来,在外层循环中对数组triangle[n][]的第二维置赋值,在内层循环中,通过条件判断分别对每行的边界元素和中间元素赋值,伪代码描述如下
for i← 0 to numRows//外层循环
triangle[i]←new int[i+1]//动态生成第二维
for j← 0 to i//内层循环
if(i==0||j==0||j==i)
triangle[i][j]←1//边界赋值
else
trangle[i][j]←(triangle[i - 1][j]+ triangle[i - 1][j - 1])//中间元素赋值
print trangle[i][j]//打印出数组元素
以下给出Java实现的代码:
/*
* 控制台打印方法
*/
public static void generate2(int numRows) {
int triangle[][] = new int[numRows][];// 创建二维数组
// 遍历二维数组的第一层
for (int i = 0; i < triangle.length; i++) {
triangle[i] = new int[i + 1];// 初始化第二层数组的大小
// 遍历第二层数组
for (int j = 0; j <= i; j++) {
// 将两侧的数组元素赋值为1
if (i == 0 || j == 0 || j == i) {
triangle[i][j] = 1;
} else {// 其他数值通过公式计算
triangle[i][j] = triangle[i - 1][j]
+ triangle[i - 1][j - 1];
}
System.out.print(triangle[i][j] + " "); // 输出数组元素
}
System.out.println(); // 换行
}
}`
经过以上代码的书写,我们已经成功地正确的帕斯卡三角结果输出,接下来就是要考虑采取以何种数据结构来保存中间结果,并最终将结果集合返回。
Java的集合框架给我们提供了很多方式。这里我们采用List集合保存最终的结果,在具体的实现中,每一行的结果也得需要List集合来保存结果,最终的结果List集合实际是存储了每一行List集合的集合。需要注意的是,List是java.util包的一个接口,代码中需要用它的实现类来创建它的对象。
下面给出最终的实现代码:
/*
* Pascal’s Triangle I,空间复杂度O(n²),时间复杂度O(n²)
*/
public static List<List<Integer>> generate1(int numRows) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
// 不合法的输入
if (numRows <= 0)
return result;
List<Integer> pre = new ArrayList<Integer>();
pre.add(1);
result.add(pre);
for (int i = 2; i <= numRows; i++) {
List<Integer> cur = new ArrayList<Integer>();
cur.add(1); // 开头元素
for (int j = 0; j < pre.size() - 1; j++) {
cur.add(pre.get(j) + pre.get(j + 1)); // 中间位置
}
cur.add(1);// 末尾元素
result.add(cur);
pre = cur;
}
return result;
}
特别说明:上述代码中,我们采用了一个中间List集合pre,用来保存初始结果以及用来生成下一行结果。之所以这样做,是因为集合和数组毕竟不同,不能预先指定大小,并且在Java中不能通过下标索引直接访问到集合中的结果,需要通过List集合提供的get(int index)方法来获取集合当中的元素。
以上算法的时间复杂度为O(n²),空间复杂度也是O(n²)。
问题变形
原文描述:
Given an index k, return the kth row of the Pascal’s triangle.For example, given k = 3,Return [1,3,3,1] .
Note: Could you optimize your algorithm to use only O ( k ) extra space?
大意:给定一个K值,返回帕斯卡三角的第K行的结果。例如,当k=3时,返回[1,3,3,1].要求空间复杂度O(n).
思路分析
变形后的题目要求我们返回第K行的结果,并且只能使用O(k)额外的辅助空间。很显然,这就意味着我们在算法中只能使用一个List集合,算法的所有操作必须围绕这个集合来展开。同样我们还是必须利用双层循环,外层循环用来控制层数,内存循环用来进行当前层结果集合的更新。但是关键的问题就出现了,如何在单个中进行值的更新。在最初的问题当中,我们是采用了辅助的集合来保存上一行的结果,但是现在除了结果集合外,不能再使用了辅助空间了,所以我们必须另找思路。假设当前集合中的元素集为[1,3,3,1],下一行的结果[1,4,6,4,1]应该如何产生?很容易想到的是,可以从一个中间元素3开始,假设当前位置为j+1,依次读取result[j] (1),result[j+1] (3)结果,然后把两者之和赋给result[j+1],这样我们就得到了一次更新结果[1,4,3,1],但是继续往下这样做的时候,会出现问题,发现下次更新的时候,result[j]的变化了,变成了4,更新结果变成了[1,4,5,1],这样显然是错误的,原因就在于我们已经赋值,原来的位置的值被覆盖掉,那么可不可以也用临时变量保存这个覆盖之前的值呢?细细分析以下,发现这样做也是不可行的,因为我们一次更新之后必定会覆盖掉result[j+1]原本位置的值,程序要维护的不是几个临时变量那么简单,而是需要整个上一行的集合。
转变思路,如何避免这个值被覆盖?既然从头部开始行不通,那么我们考虑从尾部开始试试,使j初始化为result.size()-2,还是假设当前集合为[1,3,3,1],从最后一个元素1开始,假设当前位置为j+1,依次读取result[j] (1),result[j+1] (3)结果,然后把两者之和赋给result[j+1],这样我们就得到了一次更新结果[1,3,3,4],j递减,下次更新结果为[1,3,6,4],依次类推,结果为[1,4,6,4],最终将1添加到末尾,即可完成结果集合的更新。
下面给出代码:
/*
* Pascal’s Triangle II,空间复杂度O(n),时间复杂度O(n²)
*/
public static List<Integer> getRow1(int rowIndex) {
List<Integer> result = new ArrayList<Integer>();
if (rowIndex <= 0)
return result;
result.add(1);
if (rowIndex == 1)
return result;
for (int i = 1; i < rowIndex; i++) {
for (int j = result.size() - 2; j >= 0; j--) {
// 赋值前
// System.out.println("赋值前:" + result);
result.set(j + 1, result.get(j) + result.get(j + 1));// 从右到左
// 赋值后
// System.out.println("赋值后:" + result);
}
result.add(1);
}
return result;
}
说明:上述算法的时间复杂度为O(n²),而空间复杂度为O(n)。
友情提示:由于作者水平和经验所限,博文之中难免有疏漏,欢迎大家批评指正。