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() 截取所需部分。

思路如下:

  1. 构建一个包含最多空格和最多星号的字符串
  2. 每一行从该字符串中截取对应片段

例如当 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 中打印两种常见三角形的方法:

  1. 直角三角形:逻辑最简单,适合初学者快速上手。
  2. 等腰三角形
    • 标准解法:嵌套 for 循环,控制空格和星号数量
    • 优化解法:借助 StringUtils.repeat() 减少代码量
    • 黑科技解法:用 substring 截取预构建字符串,适合炫技或性能优化

虽然这些题目看起来像是“玩具代码”,但在实际开发中,类似的字符串格式化、日志对齐、CLI 工具界面渲染等场景都有潜在应用价值。

🔗 所有示例代码已托管至 GitHub:https://github.com/tech-tutorial/triangle-examples


原始标题:Creating a Triangle with for Loops in Java