数学方法 03

305 阅读1分钟

WvlGfU.png

方法一:递推

杨辉三角具有以下性质:

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

依据性质 4,我们可以一行一行地计算杨辉三角。每当我们计算出第 i行的值,我们就可以在线性时间复杂度内计算出第 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行的数据,因此可以使用滚动数组的思想优化空间复杂度。

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

进一步优化

能否只用一个数组呢?

只用一个数组的优化,思想类似于01背包问题中的优化,需要用逆向思维,每一行的数值都从右往左求出,这样在下一行中求相同列值时,既可以直接读出上一行用数组保留的值,计算后,也可以在原数组的基础上直接覆盖元素,而不影响下一个值(即左边的元素)的计算。

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

方法二:线性递推

Cnm=n!m!(nm)!\mathcal{C}_{n}^{m}=\frac{n !}{m !(n-m) !}

得:

Cnm=Cnm1×nm+1m\mathcal{C}_{n}^{m}=\mathcal{C}_{n}^{m-1} \times \frac{n-m+1}{m}

而C(0, n) = 1,则利用上式可以在线性时间内计算出第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;
    }
}。