题目:
知识点:
杨辉三角
是一个经典的编程练习题,它完美地结合了循环控制 、二维数组(或向量) 和组合数学 的知识。其核心规律是:每个数等于它上方两数之和。
一、核心规律与性质
-
三角结构 :第一行有 1 个元素,第
n
行有n
个元素。 -
边界条件:每行的第一个和最后一个元素都是 1。
- 用代码表示即:
a[i][0] = 1;
a[i][i] = 1;
- 用代码表示即:
-
递推关系(核心公式):中间的任何元素等于其"左上方的数"与"正上方的数"之和。
- 用代码表示即:
a[i][j] = a[i-1][j-1] + a[i-1][j];
(其中i
为行号,j
为列号)
- 用代码表示即:
二、实现所需的关键 C++ 知识点
-
循环嵌套:必须使用双重循环。
-
外层循环 (
i
) :控制行数(例如,从 0 到n-1
)。 -
内层循环 (
j
) :控制每行中的列数(例如,从 0 到i
,因为第i
行有i+1
个元素)。
-
-
数据结构的选择:
-
静态二维数组:最直观的方式。
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 }
-
优点:大小可动态变化,无需预先分配固定空间。
-
缺点:语法稍复杂。
-
-
-
输出格式化:为了打印出漂亮的三角形,通常需要处理空格。
-
在每行数字前输出一定数量的空格,
(总行数 - 当前行号)
的数量是常见的策略。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;
}
四、延伸知识点与变体
-
组合数性质 :杨辉三角的第
n
行第m
列的数(从0开始计数)正好等于组合数C(n, m)
。这使得它成为计算组合数学问题的一个可视化工具。 -
优化空间复杂度 :上面的算法空间复杂度为 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; }
-
与二项式定理的联系 :
(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;
}
八、注意事项
-
空数组检查 :访问前总是检查
if (!matrix.empty())
-
不规则数组:不同行的尺寸可能不同,访问时要小心
-
性能考虑:对于性能关键的应用,考虑使用一维数组模拟二维数组
-
传递参数 :使用
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,然后只需要修改中间的元素
避免逐个赋值:比手动设置每个元素更简洁高效
等价的其他写法
// 写法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);
第一种写法是最简洁和高效的,因为它只在构造时一次性完成初始化和赋值。
题解:
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;
}
};