1. 简介
本文将介绍几种在 Java 中打印三角形的实现方式。
三角形的类型有很多,但这里我们只聚焦两种常见的:直角三角形 和 等腰三角形。这两种在算法练习和面试题中出现频率较高,掌握它们的打印逻辑能帮你避开一些“看似简单却踩坑”的问题。
2. 打印直角三角形
直角三角形是最基础的形态。目标输出如下:
*
**
***
****
*****
观察规律:
- 共
N
行(示例中N = 5
) - 第
r
行有r
个星号 ✅
也就是说:每行打印的星号数量等于当前行号。
实现思路非常简单粗暴:使用嵌套 for
循环,外层控制行数,内层控制每行星号数量。
public static String printARightTriangle(int N) {
StringBuilder result = new StringBuilder();
for (int r = 1; r <= N; r++) {
for (int j = 1; j <= r; j++) {
result.append("*");
}
result.append(System.lineSeparator());
}
return result.toString();
}
⚠️ 注意:使用 StringBuilder
拼接字符串是最佳实践,避免在循环中使用 +
拼接造成性能问题。
3. 打印等腰三角形
等腰三角形稍微复杂一点,目标输出如下:
*
***
*****
*******
*********
关键点在于:每一行不仅要打印星号,还要在前面补上适当数量的空格,才能实现居中对齐的视觉效果。
我们来拆解一下每行的构成:
行号 r |
空格数 | 星号数 |
---|---|---|
1 | 4 | 1 |
2 | 3 | 3 |
3 | 2 | 5 |
4 | 1 | 7 |
5 | 0 | 9 |
可以归纳出规律:
- 空格数 =
N - r
✅ - 星号数 =
2 * r - 1
✅
3.1 使用嵌套 for 循环
基于上述公式,我们可以写出标准解法:
public static String printAnIsoscelesTriangle(int N) {
StringBuilder result = new StringBuilder();
for (int r = 1; r <= N; r++) {
for (int sp = 1; sp <= N - r; sp++) {
result.append(" ");
}
for (int c = 1; c <= (r * 2) - 1; c++) {
result.append("*");
}
result.append(System.lineSeparator());
}
return result.toString();
}
✅ 优点:逻辑清晰,易于理解
❌ 缺点:嵌套层数多,代码略显冗长
3.2 使用单层 for 循环 + StringUtils
我们可以借助 Apache Commons Lang 3 提供的 StringUtils.repeat()
方法简化代码。
这个方法可以重复生成指定字符,比如 StringUtils.repeat('*', 5)
返回 "*****"
。
public static String printAnIsoscelesTriangleUsingStringUtils(int N) {
StringBuilder result = new StringBuilder();
for (int r = 1; r <= N; r++) {
result.append(StringUtils.repeat(' ', N - r));
result.append(StringUtils.repeat('*', 2 * r - 1));
result.append(System.lineSeparator());
}
return result.toString();
}
✅ 优点:代码简洁,可读性强
⚠️ 注意:需引入依赖:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
3.3 使用 substring 技巧
更“骚”的一种写法是预构建一个“辅助字符串”,然后通过 substring()
截取所需部分。
思路如下:
- 构建一个包含最多空格和最多星号的字符串
- 每一行从该字符串中截取对应片段
例如当 N = 5
时:
- 最多需要
N - 1 = 4
个空格 - 最多需要
2 * N - 1 = 9
个星号 - 构建
helperString = " *********"
(4空格+9星号)
然后通过 substring(r, N + 2 * r)
定位每行起始和结束位置。
完整实现:
public static String printAnIsoscelesTriangleUsingSubstring(int N) {
StringBuilder result = new StringBuilder();
String helperString = StringUtils.repeat(' ', N - 1) + StringUtils.repeat('*', N * 2 - 1);
for (int r = 0; r < N; r++) {
result.append(helperString.substring(r, N + 2 * r));
result.append(System.lineSeparator());
}
return result.toString();
}
✅ 优点:只用一个循环,代码紧凑
⚠️ 缺点:可读性较差,需要花时间理解下标计算逻辑
💡 小技巧:这种“预构建+截取”的模式在某些性能敏感场景下反而更快,因为避免了多次字符串拼接。
4. 复杂度分析
我们来对比三种实现的时间和空间复杂度。
时间复杂度
- 方法1 & 2(嵌套循环):外层
N
次,内层平均O(N)
,总时间复杂度为 O(N²) - 方法3(StringUtils / substring):虽然只有单层循环,但
repeat()
和substring()
内部操作都是O(N)
,因此总时间复杂度仍为 O(N²)
❗ 结论:没有银弹,三种方法时间复杂度一致。
空间复杂度
- StringBuilder 存储结果:最终要拼出整个三角形,字符总数约为
N²/2
,因此空间复杂度为 O(N²) - 如果直接打印(不返回字符串):
- 方法1 & 2:O(1) 辅助空间
- 方法3:由于
helperString
长度为O(N)
,空间复杂度为 O(N)
📌 总结: | 方法 | 时间复杂度 | 空间复杂度(返回字符串) | 空间复杂度(直接打印) | |------|------------|--------------------------|------------------------| | 嵌套 for | O(N²) | O(N²) | O(1) | | StringUtils | O(N²) | O(N²) | O(N) | | substring | O(N²) | O(N²) | O(N) |
5. 总结
本文系统地介绍了在 Java 中打印两种常见三角形的方法:
- 直角三角形:逻辑最简单,适合初学者快速上手。
- 等腰三角形:
- 标准解法:嵌套 for 循环,控制空格和星号数量
- 优化解法:借助
StringUtils.repeat()
减少代码量 - 黑科技解法:用
substring
截取预构建字符串,适合炫技或性能优化
虽然这些题目看起来像是“玩具代码”,但在实际开发中,类似的字符串格式化、日志对齐、CLI 工具界面渲染等场景都有潜在应用价值。
🔗 所有示例代码已托管至 GitHub:https://github.com/tech-tutorial/triangle-examples