本系列可作为JAVA学习系列的笔记,文中提到的一些练习的代码,小编会将代码复制下来,大家复制下来就可以练习了,方便大家学习。
点赞关注不迷路!您的点赞、关注和收藏是对小编最大的支持和鼓励!
系列文章目录
JAVA数据结构 DAY9 equals、Comparable、Comparator 与 PriorityQueue 深度解析
拓展目录
手把手教你用 ArrayList 实现杨辉三角:从逻辑推导到每行代码详解
Java 中的 hashCode () 与 equals () 核心原理、契约规范、重写实践与面试全解
目录
目录
5.1 为什么选择 ArrayList 而不是 LinkedList?
前言
小编作为新晋码农一枚,会定期整理一些写的比较好的代码,作为自己的学习笔记,会试着做一下批注和补充,如转载或者参考他人文献会标明出处,非商用,如有侵权会删改!欢迎大家斧正和讨论!
杨辉三角是经典的数学与编程结合案例,其 “每行首尾为 1、中间元素等于上一行左右两元素之和” 的规律,恰好能发挥 ArrayList 动态数组的优势 —— 支持随机访问(快速获取上一行元素)和动态扩容(适配每行长度递增的特点)。
本文将从杨辉三角的规律分析入手,逐行推导代码的形成逻辑,不仅教你实现功能,更让你理解 “为什么要这么写”,适合 Java 初学者巩固 ArrayList 用法和逻辑思维能力。

一、杨辉三角的核心规律(代码逻辑的基础)
在写代码前,必须先明确杨辉三角的数学规律,这是所有代码的出发点:
- 第 1 行只有 1 个元素:
[1]; - 从第 2 行开始,每行元素个数 = 行号(第 2 行 2 个元素,第 3 行 3 个元素,…,第 n 行 n 个元素);
- 每行的第 1 个元素和最后 1 个元素都是 1;
- 每行的中间元素(第 j 个,j 从 1 到行号 - 2,0 开始计数)= 上一行第 j-1 个元素 + 上一行第 j 个元素。
示例(前 5 行杨辉三角):
plaintext
第0行(索引0):[1]
第1行(索引1):[1, 1]
第2行(索引2):[1, 2, 1] → 中间元素2 = 上一行[1] + 上一行[1]
第3行(索引3):[1, 3, 3, 1] → 中间元素3=上一行[1]+上一行[2],3=上一行[2]+上一行[1]
第4行(索引4):[1, 4, 6, 4, 1] → 4=1+3,6=3+3,4=3+1
二、整体实现思路拆解
要用车 ArrayList 实现杨辉三角,需先确定数据存储结构和核心流程:
2.1 数据存储结构
- 外层容器:用
ArrayList<List<Integer>>存储整个杨辉三角。因为每行都是一个整数列表,且整个三角的行数是动态的(由用户指定),ArrayList 的动态扩容特性刚好适配。 - 内层容器:用
ArrayList<Integer>存储每行数据。每行元素个数随行数递增,ArrayList 能自动扩容,无需手动管理容量。
2.2 核心流程
- 边界处理:如果用户指定的行数≤0,返回空集合(避免无效运算);
- 初始化第一行:直接添加
[1]到外层容器(第一行是固定值,无需计算); - 迭代生成后续行(从第 2 行到第 n 行):a. 获取上一行数据(用于计算当前行中间元素);b. 创建当前行的 ArrayList,先添加第一个元素 1;c. 计算中间元素(循环遍历,利用上一行的两个元素求和);d. 添加当前行最后一个元素 1;e. 将当前行添加到外层容器;
- 遍历打印结果(优化显示效果,使三角居中)。
三、逐行代码推导与详解
下面结合完整代码,逐行分析 “为什么要这么写”,从逻辑到代码一步步拆解:
3.1 完整实现代码
import java.util.ArrayList;
import java.util.List;
public class YangHuiTriangleWithArrayList {
// 方法:生成n行杨辉三角,返回外层ArrayList
public static List<List<Integer>> generate(int numRows) {
// 1. 外层容器:存储整个杨辉三角
List<List<Integer>> yangHui = new ArrayList<>();
// 2. 边界处理:行数≤0时返回空集合
if (numRows <= 0) {
return yangHui;
}
// 3. 初始化第一行:只有元素1
List<Integer> firstRow = new ArrayList<>();
firstRow.add(1);
yangHui.add(firstRow);
// 4. 迭代生成第2行到第numRows行(i从1开始,对应第2行)
for (int i = 1; i < numRows; i++) {
// 4.1 获取上一行数据(当前行i的上一行是i-1)
List<Integer> prevRow = yangHui.get(i - 1);
// 4.2 创建当前行容器
List<Integer> currRow = new ArrayList<>();
// 4.3 当前行第一个元素:固定为1
currRow.add(1);
// 4.4 计算中间元素:从索引1到索引i-1(因为最后一个元素是1,单独添加)
for (int j = 1; j < i; j++) {
// 上一行第j-1个元素 + 上一行第j个元素
int left = prevRow.get(j - 1);
int right = prevRow.get(j);
currRow.add(left + right);
}
// 4.5 当前行最后一个元素:固定为1
currRow.add(1);
// 4.6 将当前行添加到外层容器
yangHui.add(currRow);
}
// 5. 返回生成的杨辉三角
return yangHui;
}
// 主方法:测试生成并打印杨辉三角
public static void main(String[] args) {
// 指定生成10行杨辉三角(可修改行数)
int rows = 10;
List<List<Integer>> result = generate(rows);
// 遍历打印,添加前置空格使三角居中(优化显示效果)
for (List<Integer> row : result) {
// 前置空格数量 = 总行数 - 当前行元素个数(使三角居中)
for (int i = 0; i < rows - row.size(); i++) {
System.out.print(" ");
}
// 打印当前行每个元素,用空格分隔
for (int num : row) {
System.out.print(num + " ");
}
// 每行结束后换行
System.out.println();
}
}
}
3.2 逐行代码详解(按执行流程)
3.2.1 导入包与类定义
import java.util.ArrayList;
import java.util.List;
- 为什么要写?:ArrayList 和 List 都是 Java 集合框架的类 / 接口,位于
java.util包下,必须导入才能使用,否则编译器会报错。
public class YangHuiTriangleWithArrayList {
- 为什么要写?:Java 是面向对象语言,所有代码必须放在类中。类名遵循 “大驼峰命名法”,与文件名保持一致(这是 Java 的语法规范),避免编译错误。
3.2.2 核心方法generate定义
public static List<List<Integer>> generate(int numRows) {
- 方法修饰符
public static:public表示该方法可被外部调用,static表示无需创建类对象,直接通过类名调用(方便主方法测试); - 返回值
List<List<Integer>>:外层是 List,内层每个元素也是 List<Integer>,对应 “整个三角 - 每行数据” 的存储结构; - 参数
int numRows:接收用户指定的杨辉三角行数,是生成逻辑的输入条件。
3.2.3 外层容器初始化
List<List<Integer>> yangHui = new ArrayList<>();
- 为什么用
List<List<Integer>>声明?:遵循 “面向接口编程” 原则,List 是接口,ArrayList 是实现类,声明为 List 更灵活(后续可替换为其他实现类,如 LinkedList,无需修改其他代码); - 为什么初始化
new ArrayList<>()?:必须创建具体的实现类对象,否则yangHui是 null,无法添加元素。菱形运算符<>是 Java 7 + 的特性,无需重复写泛型类型(简化代码)。
3.2.4 边界处理
if (numRows <= 0) {
return yangHui;
}
- 为什么要写?:如果用户传入
numRows=0或负数,生成杨辉三角无意义,直接返回空集合,避免后续无效的循环和内存占用,提升代码健壮性。
3.2.5 初始化第一行
List<Integer> firstRow = new ArrayList<>();
firstRow.add(1);
yangHui.add(firstRow);
- 第一行是固定值
[1],无需计算,直接创建内层 ArrayList 并添加元素 1; - 为什么要添加到外层容器
yangHui?:后续生成第 2 行及以后时,需要从上一行(即外层容器中的前一个元素)获取数据,所以必须将第一行存入外层容器。
3.2.6 迭代生成后续行(核心循环)
for (int i = 1; i < numRows; i++) {
- 循环变量
i:表示当前行的索引(从 1 开始,因为第 0 行已初始化),循环条件i < numRows表示生成到第numRows-1行(共numRows行); - 为什么从
i=1开始?:第 0 行(第一行)已手动添加,后续需要生成第 1 行到第numRows-1行(对应实际的第 2 行到第numRows行)。
List<Integer> prevRow = yangHui.get(i - 1);
- 作用:获取上一行的数据,用于计算当前行的中间元素;
- 为什么用
yangHui.get(i-1)?:外层容器yangHui的索引与行索引一致,当前行是i,上一行就是i-1;ArrayList 的get(int index)方法支持随机访问,时间复杂度 O (1),能快速获取上一行数据(这是 ArrayList 比 LinkedList 适合该场景的关键)。
List<Integer> currRow = new ArrayList<>();
- 作用:创建当前行的容器,用于存储当前行的所有元素;
- 为什么每次循环都要新建?:每行数据是独立的,不能复用之前的容器(否则会导致数据覆盖)。
currRow.add(1);
- 依据杨辉三角规律:每行第一个元素固定为 1,直接添加到当前行容器。
for (int j = 1; j < i; j++) {
- 循环变量
j:表示当前行中间元素的索引(从 1 开始,因为索引 0 已添加 1); - 循环条件
j < i:当前行有i+1个元素(因为行索引i从 1 开始,第 1 行有 2 个元素,第 2 行有 3 个元素…),中间元素的索引范围是1 ~ i-1(例如第 2 行i=2,中间元素索引j=1,循环 1 次;第 3 行i=3,中间元素索引j=1、2,循环 2 次)。
int left = prevRow.get(j - 1);
int right = prevRow.get(j);
currRow.add(left + right);
- 核心逻辑:中间元素 = 上一行第
j-1个元素 + 上一行第j个元素; - 示例:第 2 行
i=2,j=1:left=prevRow.get(0)=1,right=prevRow.get(1)=1,求和得 2,添加到当前行,即[1,2]; - 为什么用
prevRow.get()?:上一行是List<Integer>类型,必须通过get(index)获取指定位置元素,ArrayList 的随机访问特性保证了该操作高效。
currRow.add(1);
- 依据杨辉三角规律:每行最后一个元素固定为 1,添加后当前行完整(例如第 2 行变为
[1,2,1])。
yangHui.add(currRow);
- 作用:将当前行添加到外层容器,完成一行的生成,为下一行计算提供 “上一行数据”。
3.2.7 主方法测试
public static void main(String[] args) {
- 主方法是 Java 程序的入口,必须声明为
public static void main(String[] args),否则 JVM 无法启动程序。
int rows = 10;
List<List<Integer>> result = generate(rows);
rows=10:指定生成 10 行杨辉三角(可修改为任意正整数,如 5、8 等);- 调用
generate(rows):生成杨辉三角,返回结果存入result变量。
for (List<Integer> row : result) {
- 增强 for 循环(foreach):遍历外层容器
result,每次取出一行数据(List<Integer> row),比普通 for 循环更简洁(无需手动获取索引)。
for (int i = 0; i < rows - row.size(); i++) {
System.out.print(" ");
}
- 作用:添加前置空格,使杨辉三角居中显示(优化用户体验);
- 空格数量计算:
总行数 - 当前行元素个数(例如总行数 10,第 0 行元素个数 1,空格数 9;第 1 行元素个数 2,空格数 8… 最终形成居中效果); - 为什么用
System.out.print(" ")?:print不换行,println换行,这里需要连续打印空格,所以用print。
for (int num : row) {
System.out.print(num + " ");
}
- 遍历当前行的每个元素,用空格分隔打印(例如第 2 行打印 “1 2 1”)。
System.out.println();
- 一行打印完成后换行,进入下一行的打印。
四、代码执行流程演示(以生成 3 行为例)
为了更直观理解,以numRows=3为例,演示代码执行过程:
- 外层容器
yangHui初始化:[]; - 边界处理:
3>0,不返回; - 初始化第一行:
firstRow=[1],yangHui=[[1]]; - 循环生成后续行(
i=1和i=2):i=1(第 2 行):prevRow=yangHui.get(0)=[1];currRow初始化,添加 1 →[1];- 中间元素循环:
j=1 < 1不成立,循环不执行; - 添加最后一个 1 →
currRow=[1,1]; yangHui.add(currRow)→yangHui=[[1], [1,1]];
i=2(第 3 行):prevRow=yangHui.get(1)=[1,1];currRow初始化,添加 1 →[1];- 中间元素循环:
j=1 < 2成立:left=prevRow.get(0)=1,right=prevRow.get(1)=1,求和 2 →currRow=[1,2];
- 添加最后一个 1 →
currRow=[1,2,1]; yangHui.add(currRow)→yangHui=[[1], [1,1], [1,2,1]];
- 打印结果:
- 第 0 行:空格数 3-1=2 → “1”;
- 第 1 行:空格数 3-2=1 → “1 1”;
- 第 2 行:空格数 3-3=0 → “1 2 1”;
- 最终输出:
1
1 1
1 2 1
五、关键技术点与注意事项
5.1 为什么选择 ArrayList 而不是 LinkedList?
- 随机访问需求:生成中间元素时,需要频繁通过索引获取上一行的元素(
prevRow.get(j-1)和prevRow.get(j)),ArrayList 的get方法是 O (1),LinkedList 是 O (n),效率天差地别; - 动态扩容适配:每行元素个数递增,ArrayList 自动扩容,无需手动管理容量,而 LinkedList 的节点开销更大(每个节点需存储 prev/next 引用)。
5.2 常见错误与避坑指南
- 循环条件错误:中间元素循环
j < i写成j <= i→ 导致索引越界(上一行没有j=i的元素); - 忘记初始化第一行:直接进入循环生成
i=1的行,prevRow=yangHui.get(0)会报IndexOutOfBoundsException; - 没有边界处理:传入
numRows=0时,返回的yangHui是空集合,打印时无输出(合理),但如果不处理,后续循环也不会执行,不会报错,只是逻辑不完整; - 打印时没有居中:直接打印会导致三角靠左,影响可读性,添加前置空格是优化体验的关键。
5.3 扩展功能建议
- 生成指定行的杨辉三角(而非前 n 行):例如只生成第 5 行(
[1,4,6,4,1]),可优化代码,无需存储整个三角,只需保留上一行数据,减少空间开销; - 限制元素显示格式:例如每个元素占固定宽度(如
System.out.printf("%3d", num)),使三角更整齐; - 支持更大行数:当行数较多时(如 100 行),居中空格可调整为
(rows - row.size()) * 2,避免元素重叠。
六、总结
本文通过 “规律分析→思路拆解→逐行推导→流程演示” 的步骤,详细讲解了用 ArrayList 实现杨辉三角的完整过程。核心是利用 ArrayList 的两个优势:
- 随机访问高效(快速获取上一行元素,支撑中间元素计算);
- 动态扩容(适配每行元素个数递增的特点)。
代码的每一行都有其逻辑依据,从数据结构选择到循环条件设计,再到边界处理和显示优化,都是围绕杨辉三角的规律和 ArrayList 的特性展开。希望通过本文,你不仅能掌握杨辉三角的实现,更能理解 “为什么这么写”,从而提升逻辑思维和代码编写能力。
总结
以上就是今天要讲的内容,本文简单记录了java数据结构,仅作为一份简单的笔记使用,大家根据注释理解,您的点赞关注收藏就是对小编最大的鼓励!

被折叠的 条评论
为什么被折叠?



