Leecode_119.杨辉三角Ⅱ

1. 题目:

给定一个非负索引 rowIndex,返回「杨辉三角」的第 rowIndex 行。

在「杨辉三角」中,每个数是它左上方和右上方的数的和。
请添加图片描述

1.1 示例:

示例 1:
输入: rowIndex = 3
输出: [1,3,3,1]

示例 2:
输入: rowIndex = 0
输出: [1]

示例 3:
输入: rowIndex = 1
输出: [1,1]

1.2 提示:

  • 0 <= rowIndex <= 33

2. 思路及解法:

2.1 方法1:递推

杨辉三角,是二项式系数在三角形中的一种几何排列。它是中国古代数学的杰出研究成果之一,它把二项式系数图形化,把组合数内在的一些代数性质直观地从图形中体现出来,是一种离散型的数与形的结合。

杨辉三角具有以下性质:
在这里插入图片描述
① 每行数字左右对称,由 1开始逐渐变大再变小,并最终回到 1。
【解析】这条性质十分简单,直接使用观察法就看出来了。

② 第 n行(从 0开始编号)的数字有 n+1 项,前 n 行共有
n ( n + 1 ) 2 \frac {n(n+1)}{2} 2n(n+1)个数。
【解析】继续使用观察法来观察,第0行共有1个数字,第1行共有2个数字,第2行共有3个数字…第n行共有 n+1 个数字。那么前n行一共有多少个数字呢?直接套用等差数列的前n项和就可以了。

③ 第 n行的第 m个数(从 0 开始编号)可以被表示为组合数 C(n,m),记作 C n m C_n^m Cnm, 即为从 n 个不同元素中取 m 个元素的组合数。我们可以用公式来表示它: C n m = n ! m ! ( n − m ) ! C_n^m=\frac{n!}{m!(n-m)!} Cnm=m!(nm)!n!
【解析】此处n和m有点混的同学可以直接看上图,“n”代表的是某一行,“m”代表的是具体行的某一个数字。

④ 每个数字等于上一行的左右两个数字之和,可用此性质写出整个杨辉三角。即第 n 行的第 i个数等于第 n−1 行的第 i−1 个数和第 i 个数之和。这也是组合数的性质之一,即 C n i = C n − 1 i + C n − 1 i − 1 C_n^i=C_{n−1} ^i+C_{n−1 }^{i−1} Cni=Cn1i+Cn1i1 的展开式(二项式展开)中的各项系数依次对应杨辉三角的第 n 行中的每一项。
【解析】这一条还是画图来解析来的更直观一些。
Alt

⑤ 依据性质 4,我们可以一行一行地计算杨辉三角。每当我们计算出第 i 行的值,我们就可以在线性时间复杂度内计算出第 i+1 行的值。
【解析】其实一行一行地算就相当于是递推了,已知第一行,计算出下一行…依次递推就可以了。这样就可以计算出杨辉三角所有行的数字。因为是二维数组形式,所以需要哪一行直接返回那一行的索引即可。

2.1.1 Python3代码:

class Solution:
    def generate(self, rowIndex: int) -> List[List[int]]:
        ret = list()
        for i in range(rowIndex+1):
            row = list()
            for j in range(0, i + 1):
                if j == 0 or j == i:
                    row.append(1)
                else:
                    row.append(ret[i - 1][j] + ret[i - 1][j - 1])
            ret.append(row)
        return ret[rowIndex]

【代码解析】
① 该代码主要运用的思想就是先计算出杨辉三角前“rowIndex+1行”,然后再返回第“rowIndex”行。注意"rowIndex"是索引值。
② 本题需要注意的一个细节是1.1中的示例,rowIndex = 0(示例2)时候,输出的是[1],也就是杨辉三角的第1行;rowIndex = 3(示例1)的时候,输出的是[1,3,3,1],也就是杨辉三角的第4行。所以rowIndex是索引值,故我们在外层 for 循环的 range() 3函数中,参数需要设置为(rowIndex + 1),因为 range() 函数的参数范围是左闭右开。range() 函数参数范围戳这里: python中的range()函数

2.1.2 复杂度分析:

时间复杂度:O(rowIndex2
空间复杂度:O(rowIndex2
复杂度解析参考我的上一篇文章:
链接: Leecode_118.杨辉三角

2.2 方法2:滚动数组法

算法思想:方法1中,我们是根据杨辉三角的定义和性质,先求出杨辉三角所有行的值,再具体返回某一行的值。虽然可以实现我们的需求,但是浪费了很多空间。故我们提出滚动数组法。根据性质4,杨辉三角某一行的值只跟其上一行有关,那么在存储空间层面,我们就可以只存储上一行的内容,这样既可以计算出下一行的内容,又可以节省空间。参照下图来理解:
在这里插入图片描述
看完上图后,其实我们可以发现,每一行的首位和末位一直都是1,所以我们就可以给每一行的首位和末位直接赋值1,这样就不需要花费时间去计算首位和末位。

2.2.1 Python3代码:

class Solution:
    def getRow(self, rowIndex):
       pre = []
       for i in range(rowIndex+1):
           cur = [0] * (i+1)
           cur[0] = cur[i] = 1
           for j in range(1,i):
               cur[j] = pre[j-1]+pre[j]
           pre = cur
       return pre

【代码解析】
① 首先 初始化一个 pre 空数组,用来存放“上一行”的数字。
② 变量 i 控制“杨辉三角”的行数,i = 0 计算的是杨辉三角的第1行,i = 1 计算的是杨辉三角的第2行…
③ cur = [0] * (i+1) :初始化一个全0的数组,代表着当前行的数组。通过观察,我们可以发现:每行的数字数目 = i + 1。结合下图理解该结论。
在这里插入图片描述

④ cur[0] = cur[i] = 1:这句代码将每行的首位和末位直接赋值为1,避免后续再去计算首位和末位,十分巧妙。
⑤ 内部 for 循环计算每一行除去首位和末位其他的数字,因为首位(索引值为0)和末位(索引值为i)的值已经在cur[0] = cur[i] = 1这句中初始化为1,所以不需要再去进行计算。故 j 的取值范围为 range(1,i) ,对应索引值[1,i)。

2.2.2 复杂度分析:

时间复杂度:O(rowIndex2)
【解析】双层 for 循环。
空间复杂度:O(rowIndex)
【解析】每执行一次仅开辟长度为 “rowIndex + 1”的数组。

2.3 方法3:滚动数组进阶版

思想:方法2中使用了两个数组,其实就是上一行数组和所求行数组,从而实现了空间上的优化。但是根据杨辉三角和二项式系数的关系,即: C n i = C n − 1 i + C n − 1 i − 1 C_n^i=C_{n−1} ^i+C_{n−1 }^{i−1} Cni=Cn1i+Cn1i1,可以看出本行的第 i 个数字仅仅与上一行的第 i 个数字和 第 i-1 个数字有关,所以我们可以根据上一行数字倒序计算本行数字。画个图来的更直观:
在这里插入图片描述
【解析】每一行都按照从后向前的顺序进行计算,在图中,按照①->②->③的顺序进行计算。

2.3.1 Python3代码:

class Solution:
    def getRow(self, rowIndex: int) -> List[int]:
        row = [0] * (rowIndex + 1)
        row[0] = 1
        for i in range(1, rowIndex + 1):
            for j in range(i, 0, -1):
                row[j] += row[j - 1]
        return row

【代码解析】
① 首先初始化了一个全0的数组,首位初始化为1;
② 外层 for 循环控制杨辉三角的具体行数,内层 for 循环控制某一行内部的数字。
③ range(i, 0, -1) 实现了倒序的功能,参数范围[i , 0),不需要第0位参与计算,因为 row[0] = 1 是一开始就固定好的。
④ row[j] += row[j - 1] 相当于 row[j] = row[j] + row[j - 1],按照顺序先取值 row[j]、row[j - 1],再计算 row[j] + row[j - 1],再赋值给row[j],即覆盖原始的row[j]。“先取值,再计算,再覆盖”是倒序计算的关键。

2.3.2 复杂度计算:

时间复杂度:O(rowIndex2)。

空间复杂度:O(1)。不考虑返回值的空间占用。
【解析】不考虑返回值的空间占用即不考虑初始化 row[] 数组的空间占用。那么其余的只有临时变量的空间占用,所以是常数级别。

2.4 方法4:线性递推法

思想:由组合数公式 C n m = n ! m ! ( n − m ) ! C_n^m=\frac{n!}{m!(n−m)!} Cnm=m!(nm)!n!,可以得到同一行的相邻组合数的关系,
C n m = C n m − 1 × n − m + 1 m C_n^m= \mathcal{C}_n^{m-1} \times \dfrac{n-m+1}{m} Cnm=Cnm1×mnm+1,由于 C n 0 = 1 C_n^0=1 Cn0=1,利用上述公式我们可以在线性时间计算出第 n行的所有组合数。

2.4.1 Python3代码:

class Solution:
    def getRow(self, rowIndex: int) -> List[int]:
        row = [0] * (rowIndex + 1)
        row[0] = 1
        for i in range(1, rowIndex + 1):
            row[i] = row[i - 1] * (rowIndex - i + 1) // i
        return row

2.4.2 复杂度分析:

时间复杂度:O(rowIndex)。

空间复杂度:O(1)。不考虑返回值的空间占用。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值