8.26学习日志

题目:

118. 杨辉三角 - 力扣(LeetCode)

知识点:

杨辉三角

是一个经典的编程练习题,它完美地结合了循环控制二维数组(或向量)组合数学 的知识。其核心规律是:每个数等于它上方两数之和

一、核心规律与性质

  1. 三角结构 :第一行有 1 个元素,第 n 行有 n 个元素。

  2. 边界条件:每行的第一个和最后一个元素都是 1。

    • 用代码表示即:a[i][0] = 1; a[i][i] = 1;
  3. 递推关系(核心公式):中间的任何元素等于其"左上方的数"与"正上方的数"之和。

    • 用代码表示即:a[i][j] = a[i-1][j-1] + a[i-1][j]; (其中 i 为行号,j 为列号)

二、实现所需的关键 C++ 知识点

  1. 循环嵌套:必须使用双重循环。

    • 外层循环 (i) :控制行数(例如,从 0 到 n-1)。

    • 内层循环 (j) :控制每行中的列数(例如,从 0 到 i,因为第 i 行有 i+1 个元素)。

  2. 数据结构的选择

    • 静态二维数组:最直观的方式。

      复制代码
      const int MAX = 10; // 假设最多10行
      int arr[MAX][MAX];
      • 优点:简单易懂。

      • 缺点:需要提前确定最大行数,不够灵活,可能浪费内存。

    • 动态二维数组(Vector of Vectors)推荐使用,更现代、更灵活。

      复制代码
      #include <vector>
      using namespace std;
      
      int numRows = 5;
      vector<vector<int>> triangle(numRows); // 创建有numRows行的二维向量
      
      for (int i = 0; i < numRows; ++i) {
          triangle[i].resize(i + 1); // 第i行重置大小为i+1
      }
      • 优点:大小可动态变化,无需预先分配固定空间。

      • 缺点:语法稍复杂。

  3. 输出格式化:为了打印出漂亮的三角形,通常需要处理空格。

    • 在每行数字前输出一定数量的空格,(总行数 - 当前行号) 的数量是常见的策略。

      for (int i = 0; i < n; ++i) {
      // 打印空格,使其居中
      for (int space = 0; space < n - i - 1; ++space) {
      cout << " "; // 两个空格看起来更协调
      }
      // 打印数字
      for (int j = 0; j <= i; ++j) {
      cout << triangle[i][j] << " "; // 数字间加更多空格
      }
      cout << endl;
      }

三、一个典型的实现代码(使用 Vector)

复制代码
#include <iostream>
#include <vector>
using namespace std;

void printPascalTriangle(int numRows) {
    // 1. 初始化一个二维向量
    vector<vector<int>> triangle(numRows);

    // 2. 遍历每一行
    for (int i = 0; i < numRows; i++) {
        // 重置当前行的大小
        triangle[i].resize(i + 1);

        // 3. 处理边界条件:第一个和最后一个元素为1
        triangle[i][0] = triangle[i][i] = 1;

        // 4. 使用递推公式计算中间元素
        for (int j = 1; j < i; j++) {
            triangle[i][j] = triangle[i-1][j-1] + triangle[i-1][j];
        }
    }

    // 5. 打印三角形
    for (int i = 0; i < numRows; i++) {
        // 打印前导空格以使输出居中
        for (int space = 0; space < numRows - i - 1; space++) {
            cout << "  ";
        }
        // 打印当前行的所有数字
        for (int j = 0; j <= i; j++) {
            cout << triangle[i][j] << "   ";
        }
        cout << endl;
    }
}

int main() {
    int n;
    cout << "请输入要生成的行数: ";
    cin >> n;
    printPascalTriangle(n);
    return 0;
}

四、延伸知识点与变体

  1. 组合数性质 :杨辉三角的第 n 行第 m 列的数(从0开始计数)正好等于组合数 C(n, m)。这使得它成为计算组合数学问题的一个可视化工具。

  2. 优化空间复杂度 :上面的算法空间复杂度为 O(n²)。如果只需要得到第 k 行,可以使用一维数组倒序计算来将空间复杂度优化到 O(k)。

    复制代码
    vector<int> getRow(int rowIndex) {
        vector<int> row(rowIndex + 1, 1); // 初始化长度为rowIndex+1,值全为1的数组
        for (int i = 1; i < rowIndex; i++) {
            // 从后往前计算,避免覆盖前一行的数据
            for (int j = i; j > 0; j--) {
                row[j] = row[j] + row[j - 1];
            }
        }
        return row;
    }
  3. 与二项式定理的联系(a + b)^n 展开式的各项系数就是杨辉三角的第 n 行。

总结

知识点类别 具体内容
核心算法 循环嵌套、边界条件处理、递推关系
数据结构 二维数组(静态)、vector<vector<int>>(动态,推荐)
关键公式 triangle[i][j] = triangle[i-1][j-1] + triangle[i-1][j]
输出技巧 使用循环打印前导空格以实现居中对齐
延伸知识 组合数 C(n, k)、优化空间复杂度(一维数组倒序)、二项式定理

二维 vector

vector<vector<T>> 是 C++ 中非常强大和灵活的动态二维数组实现,远比静态二维数组更实用。


一、基本概念

二维 vector 本质上是一个 vector 的 vector。可以把它想象成:

  • 一个外层 vector ,它的每个元素本身又是一个 内层 vector

  • 类似于一个动态大小的矩阵行数和列数都可以在运行时改变

二、声明与初始化

1. 空二维 vector
复制代码
vector<vector<int>> matrix; // 一个空的二维整型数组
2. 指定行数和列数
复制代码
int rows = 3, cols = 4;
vector<vector<int>> matrix(rows, vector<int>(cols)); // 3行4列,默认值0
vector<vector<int>> matrix(rows, vector<int>(cols, -1)); // 3行4列,初始值全为-1
3. 直接初始化(列表初始化 - C++11)
复制代码
vector<vector<int>> matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};
4. 不规则二维数组(每行长度不同)
复制代码
vector<vector<int>> jagged = {
    {1},
    {2, 3},
    {4, 5, 6},
    {7, 8, 9, 10}
};

三、基本操作

1. 访问元素
复制代码
vector<vector<int>> matrix = {{1,2,3}, {4,5,6}};
cout << matrix[0][1];    // 输出: 2
cout << matrix.at(1).at(2); // 输出: 6 (带边界检查)
2. 获取尺寸
复制代码
int rowCount = matrix.size();    // 行数
if (rowCount > 0) {
    int colCount = matrix[0].size(); // 第一行的列数
}
// 注意:不同行的列数可能不同!
3. 添加行
复制代码
// 添加一个空行
matrix.push_back(vector<int>());

// 添加一个有初始值的行
matrix.push_back({10, 11, 12});

// 添加一个指定大小的行
matrix.push_back(vector<int>(5, 0)); // 添加一行5个0
4. 添加列元素
复制代码
matrix[0].push_back(99); // 在第一行末尾添加元素99
5. 修改元素
复制代码
matrix[1][2] = 100; // 修改第二行第三列的元素
6. 删除操作
复制代码
matrix.pop_back();      // 删除最后一行
matrix[0].pop_back();   // 删除第一行的最后一个元素
matrix[1].clear();      // 清空第二行(行还在,但为空)

四、遍历方法

1. 下标遍历(最常用)
复制代码
for (int i = 0; i < matrix.size(); i++) {
    for (int j = 0; j < matrix[i].size(); j++) {
        cout << matrix[i][j] << " ";
    }
    cout << endl;
}
2. 范围for循环(C++11)
复制代码
for (const auto& row : matrix) {    // 遍历每一行
    for (int num : row) {           // 遍历当前行的每个元素
        cout << num << " ";
    }
    cout << endl;
}
3. 迭代器遍历
复制代码
for (auto rowIt = matrix.begin(); rowIt != matrix.end(); rowIt++) {
    for (auto colIt = rowIt->begin(); colIt != rowIt->end(); colIt++) {
        cout << *colIt << " ";
    }
    cout << endl;
}

五、内存管理与性能

1. 内存布局
  • 外层 vector 在堆上存储指向各个内层 vector 的指针

  • 每个内层 vector 在堆上有自己独立的内存块

  • 这种结构不是连续内存,可能影响缓存性能

2. 预分配空间(优化性能)
复制代码
int expectedRows = 1000;
int expectedCols = 500;

vector<vector<int>> matrix;
matrix.reserve(expectedRows); // 为外层vector预留空间

for (auto& row : matrix) {
    row.reserve(expectedCols); // 为每行预留空间
}

六、与静态数组的比较

特性 二维 Vector 静态二维数组
大小 动态可变 固定
内存管理 自动 手动/栈分配
每行长度 可以不同 必须相同
性能 稍慢(间接访问) 更快(连续内存)
灵活性
安全性 高(边界检查)

七、实用技巧

1. 快速创建单位矩阵
复制代码
int n = 4;
vector<vector<int>> identity(n, vector<int>(n, 0));
for (int i = 0; i < n; i++) {
    identity[i][i] = 1;
}
2. 转置矩阵
复制代码
vector<vector<int>> transpose(const vector<vector<int>>& mat) {
    if (mat.empty()) return {};
    
    int rows = mat.size();
    int cols = mat[0].size();
    vector<vector<int>> result(cols, vector<int>(rows));
    
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            result[j][i] = mat[i][j];
        }
    }
    return result;
}
3. 矩阵相加
复制代码
vector<vector<int>> addMatrices(const vector<vector<int>>& a, 
                               const vector<vector<int>>& b) {
    // 先检查尺寸是否相同
    if (a.size() != b.size() || a[0].size() != b[0].size()) {
        throw invalid_argument("矩阵尺寸不匹配");
    }
    
    vector<vector<int>> result(a.size(), vector<int>(a[0].size()));
    for (size_t i = 0; i < a.size(); i++) {
        for (size_t j = 0; j < a[i].size(); j++) {
            result[i][j] = a[i][j] + b[i][j];
        }
    }
    return result;
}

八、注意事项

  1. 空数组检查 :访问前总是检查 if (!matrix.empty())

  2. 不规则数组:不同行的尺寸可能不同,访问时要小心

  3. 性能考虑:对于性能关键的应用,考虑使用一维数组模拟二维数组

  4. 传递参数 :使用 const vector<vector<T>>& 来避免不必要的拷贝

vector<int> row(i + 1, 1);

分解解释

1. vector<int>

创建一个整型 (int) 的 vector(动态数组)

2. row(i + 1, 1)

这是调用 vector 的构造函数,具体是这种形式:

复制代码
vector(size_type count, const T& value);

3. 参数含义

  • 第一个参数 i + 1 :指定 vector 的大小(元素个数)

  • 第二个参数 1 :指定所有元素的初始值

具体效果

假设 i = 3

复制代码
vector<int> row(3 + 1, 1); // 等价于 vector<int> row(4, 1);

这会创建一个名为 row 的 vector,它包含:

  • 4 个元素 (因为 i + 1 = 4

  • 每个元素的值都是 1

创建后的 row vector 内容为:{1, 1, 1, 1}

在杨辉三角中的用途

在杨辉三角的实现中,这行代码通常出现在这样的上下文:

复制代码
vector<vector<int>> triangle(numRows);
for (int i = 0; i < numRows; i++) {
    vector<int> row(i + 1, 1); // 创建当前行,初始全为1
    
    // 然后只处理中间的元素(跳过首尾)
    for (int j = 1; j < i; j++) {
        row[j] = triangle[i-1][j-1] + triangle[i-1][j];
    }
    
    triangle[i] = row; // 将当前行添加到三角形中
}

为什么这样用?

  1. 自动设置边界:杨辉三角每行的第一个和最后一个元素都是 1

  2. 简化代码:先全部初始化为 1,然后只需要修改中间的元素

  3. 避免逐个赋值:比手动设置每个元素更简洁高效

等价的其他写法

复制代码
// 写法1:使用构造函数(推荐)
vector<int> row(i + 1, 1);

// 写法2:先创建后赋值
vector<int> row(i + 1);
for (int k = 0; k <= i; k++) {
    row[k] = 1;
}

// 写法3:使用 assign 方法
vector<int> row;
row.assign(i + 1, 1);

第一种写法是最简洁和高效的,因为它只在构造时一次性完成初始化和赋值。

题解:

118. 杨辉三角 - 力扣(LeetCode)

复制代码
class Solution {
public:
//定义generate函数,参数是需要生成的行数numRows,返回值是二维vector(杨辉三角)
    vector<vector<int>> generate(int numRows) {
        //创建一个二维vector,用于存储杨辉三角的所有行
        vector<vector<int>> triangle;
        // 处理特殊情况:如果需要生成的行数小于等于0,直接返回空的triangle
        if (numRows <= 0) {
            return triangle;
        }
        for (int i = 0; i < numRows; ++i) {
            // 初始化当前行:有i+1个元素(第0行1个,第1行2个...),且所有元素初始化为1
            // 这一步同时处理了"每行首尾为1"的边界条件
            vector<int> row(i + 1, 1);
             // 内层循环:计算当前行的中间元素(跳过首尾的1)
            // j从1开始(跳过第0个元素),到i-1结束(跳过最后一个元素)
            for (int j = 1; j < i; ++j) {
            // 核心递推公式:当前元素 = 上一行的左上角元素 + 上一行的正上方元素
                row[j] = triangle[i - 1][j - 1] + triangle[i - 1][j];
            }
             // 将生成好的当前行添加到triangle中
            triangle.push_back(row);
        }
        // 循环结束后,返回完整的杨辉三角
        return triangle;
    }
};
相关推荐
愚润求学22 分钟前
【贪心算法】day3
c++·算法·leetcode·贪心算法
空白到白1 小时前
算法练习-合并两个有序数组
数据结构·python·算法
YuTaoShao1 小时前
【LeetCode 热题 100】75. 颜色分类——双指针
算法·leetcode·职场和发展
墨雨听阁1 小时前
8.26网络编程——Modbus TCP
网络·网络协议·学习·tcp/ip
我们从未走散2 小时前
设计模式学习笔记-----抽象责任链模式
java·笔记·学习·设计模式·责任链模式
Magnetic_h3 小时前
【iOS】内存管理及部分Runtime复习
笔记·学习·macos·ios·objective-c·cocoa·xcode
花开富贵ii3 小时前
代码随想录算法训练营四十九天|图论part07
java·数据结构·算法·图论·prim·kruscal
CoovallyAIHub4 小时前
无需ReID网络!FastTracker凭借几何与场景认知实现多目标跟踪新SOTA,助力智慧交通更轻更快
深度学习·算法·计算机视觉
CoovallyAIHub4 小时前
D‘RespNeT无人机图像分割数据集与YOLOv8-DRN模型,实时识别入口与障碍,助力灾后救援
深度学习·算法·计算机视觉