每日一题:杨辉三角的生成与调试过程全解析

引言

杨辉三角是计算机科学中一个经典的数学问题,广泛应用于组合数学和动态规划等领域。生成杨辉三角的过程看似简单,但在实际编码中,往往会遇到各种问题,尤其是当输入的 numRows 较大时,可能会遇到整数溢出的问题。本文将详细介绍如何通过调试和优化代码来解决这些问题,并最终生成正确的杨辉三角。


1. 问题描述

给定一个整数 numRows,生成杨辉三角的前 numRows 行。杨辉三角的每一行的第一个和最后一个元素都是1,中间的元素是上一行相邻两个元素之和。

numRows = 5 为例,程序的输出结果为:

[
  [1],
  [1, 1],
  [1, 2, 1],
  [1, 3, 3, 1],
  [1, 4, 6, 4, 1]
]

2. 初始代码与问题分析

根据多项式展开定理,用数学公式输出,主要考察二维数组的插入和输出。

图片名称 该公式功能代码实现如下:
int combination( int x,int y){
        long long int num_1 =1;
        
        if(y ==0 || y==x){
            return 1;
        }

        int num_2 = 1;
        for(int i =x;i>x-y;i--){
            num_1 = num_1 * i;
            num_1 = num_1/num_2;
            num_2 ++;
        }
        
        return num_1 ;
    }

对于主程序:

  1. 定义一个 generate 函数,该函数接受一个整数 numRows 作为参数,表示要生成的行数。

  2. 函数内部首先检查 numRows 是否小于 1,如果是,则返回一个空的结果向量。

  3. 接着,使用一个循环遍历从 0 到 numRows - 1 的每一行。

  4. 在每一行中,如果是第一行(即 i == 0),则将其初始化为 {1} 并添加到结果中。

  5. 对于其他行,首先清空 hang 向量,然后通过另一个循环计算当前行的每个元素,这些元素是通过调用 combination 函数计算得到的。combination 函数用于计算组合数,使用公式 C(x, y) = x! / (y! * (x - y)!),返回组合数的值。

class Solution {
public:
    vector<vector<int> > generate(int numRows) {
        vector<vector<int> > result;
        if(numRows < 1){
            return result;
        }
        vector<int> hang;
    
        for(int i=0; i<numRows;i++){
            if (i ==0 ) {
                hang = {1};
                result.push_back(hang);
            }else{
                hang.clear();
                for(int j=0;j<=i;j++){
                    long int a = combination(i,j);
                    hang.push_back(a);
                }
                result.push_back(hang);
            }  
        }
        return result;
    }

    int combination( int x,int y){
        long long int num_1 =1;
        
        if(y ==0 || y==x){
            return 1;
        }

        int num_2 = 1;
        for(int i =x;i>x-y;i--){
            num_1 = num_1 * i;
            num_1 = num_1/num_2;
            num_2 ++;
        }
        
        return num_1 ;
    }
};
语法/功能代码示例说明
二维向量声明vector<vector<int> > result;声明一个二维整数向量 result,用于存储生成的杨辉三角。
条件判断if(numRows < 1)如果 numRows 小于 1,直接返回空的 result
一维向量声明vector<int> hang;声明一个一维整数向量 hang,用于存储每一行的数据。
向量初始化hang = {1};初始化 hang 向量为 {1}
向量插入result.push_back(hang);hang 向量插入到 result 中。
循环结构for(int i=0; i<numRows;i++)使用 for 循环生成每一行的数据。
组合数计算long int a = combination(i,j);调用 combination 函数计算组合数。
组合数函数int combination( int x,int y)定义一个计算组合数的函数。
条件判断`if(y ==0
循环结构for(int i =x;i>x-y;i--)使用 for 循环计算组合数。
变量声明long long int num_1 =1;声明一个长整型变量 num_1 并初始化为 1。
变量声明int num_2 = 1;声明一个整型变量 num_2 并初始化为 1。

数组插入的语法扩展

在C++中,vector 是一种动态数组,提供了多种插入元素的方法。以下是一些常用的插入语法:

  1. push_back 方法

    • 语法:vector.push_back(value);
    • 示例:hang.push_back(a);
    • 说明:将 value 插入到 vector 的末尾。
  2. insert 方法

    • 语法:vector.insert(position, value);
    • 示例:hang.insert(hang.begin() + 2, 5);
    • 说明:在 position 位置插入 value

在初始代码中,我们使用了组合数公式来计算杨辉三角中的每个元素。然而,当 numRows 较大时(例如 numRows = 20),组合数的计算会导致整数溢出,即使使用了 long long int 也无法避免,阶乘爆炸对于计算机而言是常见的事情。

对于数组元素的插入,push_back还是insert更多内容请参考:C++插入元素全攻略:字符串、数组、链表、队列,一文搞定![特殊字符]


3. 调试过程

相比较于算法的改进,如何通过debug寻找到这个问题更为重要。

  1. 发现问题:当 numRows = 15 时,生成的杨辉三角出现了错误。最开始的combination函数简单粗暴,为阶乘相乘除。不难发现,第15行数组显然不对称,且数据不对

在这里插入图片描述

  1. 稍微修改代码 :让循环直接从第十四行开始:
    在这里插入图片描述

  2. 通过打断点调试:在关键地方打上断点,逐步运行,发现num_1变量溢出。
    在这里插入图片描述

  3. 第一次优化:将 int 改为 long int,但问题依然存在,当 numRows = 20 时,仍然出现溢出。

  4. 第二次优化:将 long int 改为 long long int,但问题依然没有解决。数值溢出导致返回的值a不对,插入数组元素不符合预期,导致运行失败。

在这里插入图片描述

  1. 最终解决方案:通过优化组合数计算的算法,避免了直接计算大数组合数,从而解决了溢出问题。
   int combination( int x,int y){
        long long int num_1 =1;
        
        if(y ==0 || y==x){
            return 1;
        }

        int num_2 = 1;
        for(int i =x;i>x-y;i--){
            num_1 = num_1 * i;
            num_1 = num_1/num_2;
            num_2 ++;
        }
        
        return num_1 ;
    }
4. 其他解法 —— 不依靠二项式公式

4.1 代码

vector<vector<int> > generate(int numRows)
{
    vector<vector<int>> res;
    if(numRows==0)
        return res;
    vector<int> first;
    first.push_back(1);
    res.push_back(first);
    if(numRows==1)
        return res;
    for(int i=1; i<numRows; i++)
    {
        vector<int> tmp;
        tmp.push_back(1);
        for(int j=i-1; j>=1; j--)
            tmp.push_back(res[i-1][j]+res[i-1][j-1]);
        tmp.push_back(1);
        res.push_back(tmp);
    }
    return res;
}

4.2 编程思路

  1. 初始化

    • 创建一个二维向量 res 用于存储最终的结果。
    • 如果 numRows 为 0,直接返回空的 res
  2. 处理第一行

    • 创建一个一维向量 first,并将 1 插入其中。
    • first 插入到 res 中。
    • 如果 numRows 为 1,直接返回 res
  3. 生成后续行

    • 从第二行开始(i = 1),逐行生成杨辉三角。
    • 对于每一行:
      • 创建一个临时向量 tmp,并在开头插入 1
      • 通过上一行的数据(res[i-1])计算当前行的中间值,公式为:res[i-1][j] + res[i-1][j-1]
      • tmp 的末尾插入 1
      • tmp 插入到 res 中。
  4. 返回结果

    • 最终返回生成的杨辉三角 res
语法/功能代码示例说明
临时向量声明vector<int> tmp;声明一个临时一维向量 tmp,用于存储当前行的数据。
向量插入tmp.push_back(1);1 插入到 tmp 的开头和末尾。
中间值计算tmp.push_back(res[i-1][j]+res[i-1][j-1]);通过上一行的数据计算当前行的中间值,并插入到 tmp 中。
二维向量插入res.push_back(tmp);tmp 插入到 res 中。

4.3 关键点解析

  1. 杨辉三角的性质

    • 每一行的第一个和最后一个元素都是 1
    • 中间的元素是上一行相邻两个元素之和。
  2. 向量的动态性

    • vector 是动态数组,可以随时插入元素,非常适合用于存储杨辉三角的每一行。
  3. 循环与索引

    • 外层循环控制行数,内层循环控制每一行的元素计算。
    • 通过 res[i-1] 访问上一行的数据,利用索引 jj-1 计算当前行的值。
  4. 代码简洁性

    • 代码逻辑清晰,通过逐行生成的方式避免了复杂的数学计算,直接利用上一行的结果生成当前行。

这段代码通过逐行生成的方式高效地构建了杨辉三角,充分利用了 vector 的动态性和索引操作。代码逻辑清晰,适合初学者理解和掌握二维向量的基本操作以及杨辉三角的生成原理。


结论

通过本次调试和优化,我们不仅解决了杨辉三角生成中的溢出问题,还学习了如何通过优化算法来提高代码的效率和稳定性。希望本文能帮助你在日常编程中更好地应对类似的问题。


关注专栏:C++基础语法与操作指南,一起学习C++。
关注专栏:每日一题:C++ LeetCode精讲,我们一起进步


点个赞吧

图片名称
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wujj_whut

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值