zoukankan      html  css  js  c++  java
  • 每日一道 LeetCode (29):杨辉三角 II

    每天 3 分钟,走上算法的逆袭之路。

    前文合集

    每日一道 LeetCode 前文合集

    代码仓库

    GitHub: https://github.com/meteor1993/LeetCode

    Gitee: https://gitee.com/inwsy/LeetCode

    题目:杨辉三角

    题目来源:https://leetcode-cn.com/problems/pascals-triangle-ii/

    给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行。

    在杨辉三角中,每个数是它左上方和右上方的数的和。

    示例:

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

    进阶:

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

    解题方案一:暴力求解

    这道题和昨天那道题非常像,基本上昨天那道题返回结果稍微改动下就可以了。

    先看下修改自昨天的那个答案:

    public List<Integer> getRow(int numRows) {
        List<List<Integer>> triangle = new ArrayList<List<Integer>>();
        // 直接给第一行赋值,永远是 1
        triangle.add(new ArrayList<>());
        triangle.get(0).add(1);
    
        for (int i = 1; i <= numRows; i++) {
            // 定义当前行
            List<Integer> row = new ArrayList<>();
            // 定义上一行
            List<Integer> prevRow = triangle.get(i - 1);
            // 当前行开头为 1
            row.add(1);
            // 计算每一个数值,结果是上一行的 j 和 j + 1 位置的和
            for (int j = 1; j < i; j++) {
                row.add(prevRow.get(j - 1) + prevRow.get(j));
            }
            // 当前行添加末尾数字 1
            row.add(1);
            // 把当前行添加到要返回的列表中
            triangle.add(row);
        }
        return triangle.get(numRows);
    }
    

    这么做其实有点浪费,我们把每一行的结果全记了下来,实际上我们只需要记录上一行的结果就可以了,那么代码可以稍微优化一下:

    public List<Integer> getRow(int numRows) {
        List<Integer> row = new ArrayList<> ();
        List<Integer> prevRow = new ArrayList<> ();
        for (int i = 0; i <= numRows; i++) {
            row = new ArrayList<> ();
            for (int j = 0; j <= i; j++) {
                if (j == 0 || j == i) {
                    row.add(1);
                } else {
                    row.add(prevRow.get(j - 1) + prevRow.get(j));
                }
            }
            prevRow = row;
        }
        return row;
    }
    

    这段代码中,我没有再保存杨辉三角所有的结果,只保留了当前行 row 和上一行 prevRow 。通过每次循环中,当前行赋值给上一行,来求解下一行的数据,当然,时间复杂度和前面的方案是一样的,只是节省了一定程度上的空间。

    解题方案二:通项公式求解

    这个方案是看了答案以后才知道的,我原以为还有什么我想不到的优化方案,结果是通过数学的方式总结出来通项公式直接进行计算。

    杨辉三角实际上是组合数构成的:

    如果要求其中某一个数字 (C_n^k) 有一个通项公式如下:

    [C_n^k = n!/(k!(n - k)!) = (n * (n - 1) * (n - 2) * ... (n - k + 1))/k! ]

    我们可以根据这个通项公式来写算法。

    public List<Integer> getRow_2(int numRows) {
        List<Integer> list = new ArrayList<>();
        int N = numRows;
        for (int k = 0; k <= N; k++) {
            list.add(combination(N, k));
        }
        return list;
    }
    
    private int combination(int N, int k) {
        long res = 1;
        for (int i = 1; i <= k; i++) {
            res = res * (N - k + i) / i;
        }
        return (int) res;
    }
    

    这个方案我们是按照通项公式,对每一项都进行了从头到尾的计算得出来的结果,实际上这个组合数是有联系的,如下:

    [C_n^k = C_n^{k - 1} * (n - k + 1)/k ]

    这样我们就不用每次都从头计算每个数字了,只需要将前一个数字记录下来,就可以通过上面这个公式直接求解。

    public List<Integer> getRow_3(int numRows) {
        List<Integer> list = new ArrayList<>();
        int N = numRows;
        long pre = 1;
        list.add(1);
        for (int k = 1; k <= N; k++) {
            long cur = pre * (N - k + 1) / k;
            list.add((int) cur);
            pre = cur;
        }
        return list;
    }
    

    这个算法到这里就结束了么?当然没有,这个还能接着优化,因为杨辉三角的每一行都是左右对称的,我们没有必要所有的值都求出来,可以只计算一半,后面一半由前面一半直接进行补充。

    public List<Integer> getRow_4(int numRows) {
        List<Integer> list = new ArrayList<>();
        int N = numRows;
        long pre = 1;
        list.add(1);
        for (int k = 1; k <= N; k++) {
            if (k <= (N + 1) / 2) {
                long cur = pre * (N - k + 1) / k;
                list.add((int) cur);
                pre = cur;
            } else {
                list.add(list.get(N - k));
            }
        }
        return list;
    }
    

    果然在答案中是能人辈出,我一个大写的佩服。

    扫描二维码关注「极客挖掘机」公众号!
    作者:极客挖掘机
    定期发表作者的思考:技术、产品、运营、自我提升等。

    本文版权归作者极客挖掘机和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
    如果您觉得作者的文章对您有帮助,就来作者个人小站逛逛吧:极客挖掘机
  • 相关阅读:
    立 Flag
    ASP.NET Core 3.0 一个 jwt 的轻量角色/用户、单个API控制的授权认证库
    C# Xamarin 数据绑定入门基础
    C# HttpClient 请求认证、数据传输笔记
    【PHP篇】输出方法
    【PHP篇】变量与常量
    C语言小笔记
    树莓派GPIO口的使用
    树莓派连接不上WiFi
    OLED屏幕详细使用(CC2530 & 51)
  • 原文地址:https://www.cnblogs.com/babycomeon/p/13575557.html
Copyright © 2011-2022 走看看