给定一个非负索引 rowIndex
,返回「杨辉三角」的第 rowIndex
__ 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
示例 1:
**输入:** rowIndex = 3
**输出:** [1,3,3,1]
示例 2:
**输入:** rowIndex = 0
**输出:** [1]
示例 3:
**输入:** rowIndex = 1
**输出:** [1,1]
提示:
进阶:
你可以优化你的算法到 _O_ ( _rowIndex_ )
空间复杂度吗?
方法一:递推 杨辉三角,是二项式系数在三角形中的一种几何排列。它是中国古代数学的杰出研究成果之一,它把二项式系数图形化,把组合数内在的一些代数性质直观地从图形中体现出来,是一种离散型的数与形的结合。
杨辉三角具有以下性质:
每行数字左右对称,由 $1$ 开始逐渐变大再变小,并最终回到 $1$。
第 $n$ 行(从 $0$ 开始编号)的数字有 $n+1$ 项,前 $n$ 行共有 $\frac{n(n+1)}{2}$ 个数。
第 $n$ 行的第 $m$ 个数(从 $0$ 开始编号)可表示为可以被表示为组合数 $\mathcal{C}(n,m)$,记作 $\mathcal{C}_n^m$ 或 $\binom{n}{m}$,即为从 $n$ 个不同元素中取 $m$ 个元素的组合数。我们可以用公式来表示它:$\mathcal{C}_n^m=\dfrac{n!}{m!(n-m)!}$
每个数字等于上一行的左右两个数字之和,可用此性质写出整个杨辉三角。即第 $n$ 行的第 $i$ 个数等于第 $n-1$ 行的第 $i-1$ 个数和第 $i$ 个数之和。这也是组合数的性质之一,即 $\mathcal{C}n^i=\mathcal{C} {n-1}^i+\mathcal{C}_{n-1}^{i-1}$。
$(a+b)^n$ 的展开式(二项式展开)中的各项系数依次对应杨辉三角的第 $n$ 行中的每一项。
依据性质 $4$,我们可以一行一行地计算杨辉三角。每当我们计算出第 $i$ 行的值,我们就可以在线性时间复杂度内计算出第 $i+1$ 行的值。
代码
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : vector<int > getRow (int rowIndex) { vector<vector<int >> C (rowIndex + 1 ); for (int i = 0 ; i <= rowIndex; ++i) { C[i].resize (i + 1 ); C[i][0 ] = C[i][i] = 1 ; for (int j = 1 ; j < i; ++j) { C[i][j] = C[i - 1 ][j - 1 ] + C[i - 1 ][j]; } } return C[rowIndex]; } };
[sol1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public List<Integer> getRow (int rowIndex) { List<List<Integer>> C = new ArrayList <List<Integer>>(); for (int i = 0 ; i <= rowIndex; ++i) { List<Integer> row = new ArrayList <Integer>(); for (int j = 0 ; j <= i; ++j) { if (j == 0 || j == i) { row.add(1 ); } else { row.add(C.get(i - 1 ).get(j - 1 ) + C.get(i - 1 ).get(j)); } } C.add(row); } return C.get(rowIndex); } }
[sol1-Golang] 1 2 3 4 5 6 7 8 9 10 11 func getRow (rowIndex int ) []int { C := make ([][]int , rowIndex+1 ) for i := range C { C[i] = make ([]int , i+1 ) C[i][0 ], C[i][i] = 1 , 1 for j := 1 ; j < i; j++ { C[i][j] = C[i-1 ][j-1 ] + C[i-1 ][j] } } return C[rowIndex] }
[sol1-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 var getRow = function (rowIndex ) { const C = new Array (rowIndex + 1 ).fill (0 ); for (let i = 0 ; i <= rowIndex; ++i) { C[i] = new Array (i + 1 ).fill (0 ); C[i][0 ] = C[i][i] = 1 ; for (let j = 1 ; j < i; j++) { C[i][j] = C[i - 1 ][j - 1 ] + C[i - 1 ][j]; } } return C[rowIndex]; };
[sol1-C] 1 2 3 4 5 6 7 8 9 10 11 12 int * getRow (int rowIndex, int * returnSize) { *returnSize = rowIndex + 1 ; int * C[rowIndex + 1 ]; for (int i = 0 ; i <= rowIndex; ++i) { C[i] = malloc (sizeof (int ) * (i + 1 )); C[i][0 ] = C[i][i] = 1 ; for (int j = 1 ; j < i; ++j) { C[i][j] = C[i - 1 ][j - 1 ] + C[i - 1 ][j]; } } return C[rowIndex]; }
优化
注意到对第 $i+1$ 行的计算仅用到了第 $i$ 行的数据,因此可以使用滚动数组 的思想优化空间复杂度。
[sol2-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution {public : vector<int > getRow (int rowIndex) { vector<int > pre, cur; for (int i = 0 ; i <= rowIndex; ++i) { cur.resize (i + 1 ); cur[0 ] = cur[i] = 1 ; for (int j = 1 ; j < i; ++j) { cur[j] = pre[j - 1 ] + pre[j]; } pre = cur; } return pre; } };
[sol2-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public List<Integer> getRow (int rowIndex) { List<Integer> pre = new ArrayList <Integer>(); for (int i = 0 ; i <= rowIndex; ++i) { List<Integer> cur = new ArrayList <Integer>(); for (int j = 0 ; j <= i; ++j) { if (j == 0 || j == i) { cur.add(1 ); } else { cur.add(pre.get(j - 1 ) + pre.get(j)); } } pre = cur; } return pre; } }
[sol2-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 func getRow (rowIndex int ) []int { var pre, cur []int for i := 0 ; i <= rowIndex; i++ { cur = make ([]int , i+1 ) cur[0 ], cur[i] = 1 , 1 for j := 1 ; j < i; j++ { cur[j] = pre[j-1 ] + pre[j] } pre = cur } return pre }
[sol2-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 var getRow = function (rowIndex ) { let pre = [], cur = []; for (let i = 0 ; i <= rowIndex; ++i) { cur = new Array (i + 1 ).fill (0 ); cur[0 ] = cur[i] =1 ; for (let j = 1 ; j < i; ++j) { cur[j] = pre[j - 1 ] + pre[j]; } pre = cur; } return pre; };
[sol2-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int * getRow (int rowIndex, int * returnSize) { *returnSize = rowIndex + 1 ; int * pre = malloc (sizeof (int ) * (*returnSize)); memset (pre, 0 , sizeof (int ) * (*returnSize)); int * cur = malloc (sizeof (int ) * (*returnSize)); memset (cur, 0 , sizeof (int ) * (*returnSize)); for (int i = 0 ; i <= rowIndex; ++i) { cur[0 ] = cur[i] = 1 ; for (int j = 1 ; j < i; ++j) { cur[j] = pre[j - 1 ] + pre[j]; } int * tmp = pre; pre = cur; cur = tmp; } return pre; }
进一步优化
能否只用一个数组呢?
递推式 $\mathcal{C}n^i=\mathcal{C} {n-1}^i+\mathcal{C}_{n-1}^{i-1}$ 表明,当前行第 $i$ 项的计算只与上一行第 $i-1$ 项及第 $i$ 项有关。因此我们可以倒着计算当前行,这样计算到第 $i$ 项时,第 $i-1$ 项仍然是上一行的值。
[sol3-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution {public : vector<int > getRow (int rowIndex) { vector<int > row (rowIndex + 1 ) ; row[0 ] = 1 ; for (int i = 1 ; i <= rowIndex; ++i) { for (int j = i; j > 0 ; --j) { row[j] += row[j - 1 ]; } } return row; } };
[sol3-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { public List<Integer> getRow (int rowIndex) { List<Integer> row = new ArrayList <Integer>(); row.add(1 ); for (int i = 1 ; i <= rowIndex; ++i) { row.add(0 ); for (int j = i; j > 0 ; --j) { row.set(j, row.get(j) + row.get(j - 1 )); } } return row; } }
[sol3-Golang] 1 2 3 4 5 6 7 8 9 10 func getRow (rowIndex int ) []int { row := make ([]int , rowIndex+1 ) row[0 ] = 1 for i := 1 ; i <= rowIndex; i++ { for j := i; j > 0 ; j-- { row[j] += row[j-1 ] } } return row }
[sol3-JavaScript] 1 2 3 4 5 6 7 8 9 10 var getRow = function (rowIndex ) { const row = new Array (rowIndex + 1 ).fill (0 ); row[0 ] = 1 ; for (let i = 1 ; i <= rowIndex; ++i) { for (let j = i; j > 0 ; --j) { row[j] += row[j - 1 ]; } } return row; };
[sol3-C] 1 2 3 4 5 6 7 8 9 10 11 12 int * getRow (int rowIndex, int * returnSize) { *returnSize = rowIndex + 1 ; int * row = malloc (sizeof (int ) * (*returnSize)); memset (row, 0 , sizeof (int ) * (*returnSize)); row[0 ] = 1 ; for (int i = 1 ; i <= rowIndex; ++i) { for (int j = i; j > 0 ; --j) { row[j] += row[j - 1 ]; } } return row; }
复杂度分析
方法二:线性递推 由组合数公式 $\mathcal{C}_n^m=\dfrac{n!}{m!(n-m)!}$,可以得到同一行的相邻组合数的关系
$$\mathcal{C}_n^m= \mathcal{C}_n^{m-1} \times \dfrac{n-m+1}{m}$$
由于 $\mathcal{C}_n^0=1$,利用上述公式我们可以在线性时间计算出第 $n$ 行的所有组合数。
代码
[sol4-C++] 1 2 3 4 5 6 7 8 9 10 11 class Solution {public : vector<int > getRow (int rowIndex) { vector<int > row (rowIndex + 1 ) ; row[0 ] = 1 ; for (int i = 1 ; i <= rowIndex; ++i) { row[i] = 1LL * row[i - 1 ] * (rowIndex - i + 1 ) / i; } return row; } };
[sol4-Java] 1 2 3 4 5 6 7 8 9 10 class Solution { public List<Integer> getRow (int rowIndex) { List<Integer> row = new ArrayList <Integer>(); row.add(1 ); for (int i = 1 ; i <= rowIndex; ++i) { row.add((int ) ((long ) row.get(i - 1 ) * (rowIndex - i + 1 ) / i)); } return row; } }
[sol4-Golang] 1 2 3 4 5 6 7 8 func getRow (rowIndex int ) []int { row := make ([]int , rowIndex+1 ) row[0 ] = 1 for i := 1 ; i <= rowIndex; i++ { row[i] = row[i-1 ] * (rowIndex - i + 1 ) / i } return row }
[sol4-JavaScript] 1 2 3 4 5 6 7 8 var getRow = function (rowIndex ) { const row = new Array (rowIndex + 1 ).fill (0 ); row[0 ] = 1 ; for (let i = 1 ; i <= rowIndex; ++i) { row[i] = row[i - 1 ] * (rowIndex - i + 1 ) / i; } return row; };
[sol4-C] 1 2 3 4 5 6 7 8 9 int * getRow (int rowIndex, int * returnSize) { *returnSize = rowIndex + 1 ; int * row = malloc (sizeof (int ) * (*returnSize)); row[0 ] = 1 ; for (int i = 1 ; i <= rowIndex; ++i) { row[i] = 1LL * row[i - 1 ] * (rowIndex - i + 1 ) / i; } return row; }
复杂度分析