【力扣】119. 杨辉三角 II

以下为本人思路,以及力扣官方题解

119. 杨辉三角 II

  • 题目
  • 示例
  • 进阶
  • 本人思路
  • 官方题解
    • 思路一 递推
      • 代码
      • 优化
        • 代码
      • 进一步优化
        • 代码
      • 复杂度分析
    • 思路二 线性递推
      • 代码
      • 复杂度分析

题目

给定一个非负索引 k k k,其中 k ≤ 33 k ≤ 33 k33,返回杨辉三角的第 k k k 行。
【力扣】119. 杨辉三角 II_第1张图片
在杨辉三角中,每个数是它左上方和右上方的数的和。

示例

输入: 3 3 3
输出: [ 1 , 3 , 3 , 1 ] [1,3,3,1] [1,3,3,1]

进阶

你可以优化你的算法到 O ( k ) O(k) O(k) 空间复杂度吗?

本人思路

我们知道,杨辉三角形第 i i i 层的数值可以由第 i − 1 i-1 i1 层计算得到,因此我们只需要保存两层的数值就可以计算出后面层数的数值。

官方题解

思路一 递推

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

杨辉三角具有以下性质:

  1. 每行数字左右对称,由 1 1 1 开始逐渐变大再变小,并最终回到 1 1 1
  2. n n n 行(从 0 0 0 开始编号)的数字有 n + 1 n+1 n+1 项,前 n n n 行共有 n ( n + 1 ) 2 \frac{n(n+1)}{2} 2n(n+1)​ 个数。
  3. n n n 行的第 m m m 个数(从 0 0 0 开始编号)可表示为可以被表示为组合数 C ( n , m ) \mathcal{C}(n,m) C(n,m),记作 C n m \mathcal{C}_n^m Cnm​ 或 ( n m ) \binom{n}{m} (mn),即为从 nnn 个不同元素中取 m m m 个元素的组合数。我们可以用公式来表示它: C n m = n ! m ! ( n − m ) ! \mathcal{C}_n^m=\dfrac{n!}{m!(n-m)!} Cnm=m!(nm)!n!
  4. 每个数字等于上一行的左右两个数字之和,可用此性质写出整个杨辉三角。即第 nnn 行的第 iii 个数等于第 n − 1 n-1 n1 行的第 i − 1 i-1 i1 个数和第 i i i 个数之和。这也是组合数的性质之一,即 C n i = C n − 1 i + C n − 1 i − 1 \mathcal{C}_n^i=\mathcal{C}_{n-1}^i+\mathcal{C}_{n-1}^{i-1} Cni=Cn1i+Cn1i1
  5. ( a + b ) n (a+b)^n (a+b)n 的展开式(二项式展开)中的各项系数依次对应杨辉三角的第 n n n 行中的每一项。

依据性质 4 4 4,我们可以一行一行地计算杨辉三角。每当我们计算出第 i i i 行的值,我们就可以在线性时间复杂度内计算出第 i + 1 i+1 i+1 行的值。

代码

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);
    }
}

优化

注意到对第 i + 1 i+1 i+1 行的计算仅用到了第 i i i 行的数据,因此可以使用滚动数组的思想优化空间复杂度。

代码

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;
    }
}

进一步优化

能否只用一个数组呢?

递推式 C n i = C n − 1 i + C n − 1 i − 1 \mathcal{C}_n^i=\mathcal{C}_{n-1}^i+\mathcal{C}_{n-1}^{i-1} Cni=Cn1i+Cn1i1 表明,当前行第 i i i 项的计算只与上一行第 i − 1 i-1 i1 项及第 i i i 项有关。因此我们可以 倒着 计算当前行,这样计算到第 i i i 项时,第 i − 1 i-1 i1 项仍然是上一行的值。

代码

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;
    }
}

复杂度分析

  • 时间复杂度: O ( rowIndex 2 ) O(\textit{rowIndex}^2) O(rowIndex2)
  • 空间复杂度: O ( 1 ) O(1) O(1)。不考虑返回值的空间占用。

思路二 线性递推

由组合数公式 C n m = n ! m ! ( n − m ) ! \mathcal{C}_n^m=\dfrac{n!}{m!(n-m)!} Cnm=m!(nm)!n!​,
可以得到同一行的相邻组合数的关系为: C n m = C n m − 1 × n − m + 1 m \mathcal{C}_n^m= \mathcal{C}_n^{m-1} \times \dfrac{n-m+1}{m} Cnm=Cnm1×mnm+1

由于 C n 0 = 1 \mathcal{C}_n^0=1 Cn0=1,利用上述公式我们可以在线性时间计算出第 n n n 行的所有组合数。

代码

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;
    }
}

复杂度分析

  • 时间复杂度: O ( rowIndex ) O(\textit{rowIndex}) O(rowIndex)
  • 空间复杂度: O ( 1 ) O(1) O(1)。不考虑返回值的空间占用。

你可能感兴趣的:(力扣,java,leetcode)