Pattern 3
Java source ↗ Edit this article ↗
Jump to code:
Problem
Given an integer n, print a right-angled number pyramid: n rows, where row i (counting from 1) contains the numbers 1, 2, ..., i separated by single spaces. The shape is the triangle of Pattern 2, but each star is replaced by its column number.
Output format: every row ends with a newline, numbers in a row are separated by exactly one space, and there is no trailing space. Numbers with two or more digits are printed in full (10, 11, …); there is no padding. n = 0 prints nothing.
Constraints: 0 ≤ n ≤ 100 is typical.
Example 1
n = 4
1
1 2
1 2 3
1 2 3 4
Row i counts from 1 up to i.
Example 2
n = 1
1
A single number, so there is no separator.
What changes compared to the star triangle
The loop structure is the triangle’s: outer loop over rows, inner loop from 1 to i. The new element is the cell content. Instead of a constant *, we print the inner loop counter j. This is the first pattern where the value printed is data derived from the indices rather than a fixed symbol, and the lesson generalises: once the loop skeleton is right, any pattern is just a formula from (i, j) to a character or number.
In Java there is a small trap here. StringBuilder.append(int) appends the decimal representation, so sb.append(j) writes "12" for j = 12. If you cast to char or add to '0' to “convert” a digit, you get a single character, which is wrong for j ≥ 10 and silently produces garbage. Keep numbers as int and let append or Integer.toString do the formatting.
All approaches return the pattern as a String so the tests can compare it with expected literals.
Approach 1: Nested loops
Intuition. Row i has i cells and cell j holds the value j. The counter is the content, so the body is just append(j), preceded by a space when j > 1.
Algorithm.
- For
ifrom1ton: - For
jfrom1toi: ifj > 1append a space; append the numberj. - Append
\n. - Return the string.
static String nestedLoops(int n) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
if (j > 1) sb.append(' ');
sb.append(j);
}
sb.append('\n');
}
return sb.toString();
}
Complexity. Time O(n^2): n(n+1)/2 numbers are written, each with O(log n) digits, which is a constant for any practical n. Space O(n^2) for the output; O(1) extra.
Check the boundary rows before moving on. For i = 1 the inner loop runs once and writes "1" with no separator; for i = n it writes 1 through n. If the first row comes out empty, the inner loop started at j = 1 but the outer loop started at i = 0; if the last row stops at n - 1, the inner bound was j < i instead of j <= i. Testing these two rows catches almost every off-by-one error in this family of patterns.
Approach 2: Grow one row incrementally
Intuition. Row i is row i - 1 with " i" appended: "1", "1 2", "1 2 3". So maintain a single row buffer, extend it by one number per iteration, and copy it into the output. The inner loop vanishes and the dependency on i lives in the carried state. This is also the natural way to answer “print only the k-th row” in O(k) without recomputing anything.
Algorithm.
- Keep
out(result) androw(current row) builders. - For
ifrom1ton: ifi > 1append a space torow; append the numberitorow; appendrowthen\ntoout. - Return
out.
static String growingRow(int n) {
StringBuilder out = new StringBuilder();
StringBuilder row = new StringBuilder();
for (int i = 1; i <= n; i++) {
if (i > 1) row.append(' ');
row.append(i);
out.append(row).append('\n');
}
return out.toString();
}
Complexity. Time O(n^2) because copying row costs O(i) at step i. Space O(n) extra for the row buffer.
Approach 3: Streams
Intuition. A row is “the numbers 1..i joined by spaces”. IntStream.rangeClosed(1, i) gives the numbers, mapToObj(j -> Integer.toString(j)) turns them into strings, and Collectors.joining(" ") glues them without a trailing space. An outer stream over the row indices produces and concatenates the rows. The code is a direct transcription of the definition.
Algorithm.
- Outer
IntStream.rangeClosed(1, n)over rows. - For each
i, innerIntStream.rangeClosed(1, i)mapped to strings and joined with" ", followed by\n. - Join all rows with
Collectors.joining().
static String streams(int n) {
return IntStream.rangeClosed(1, n)
.mapToObj(i -> IntStream.rangeClosed(1, i)
.mapToObj(j -> Integer.toString(j))
.collect(Collectors.joining(" ")) + "\n")
.collect(Collectors.joining());
}
Dry run for n = 3:
| i | inner stream | row |
|---|---|---|
| 1 | 1 |
"1\n" |
| 2 | 1, 2 |
"1 2\n" |
| 3 | 1, 2, 3 |
"1 2 3\n" |
Result: "1\n1 2\n1 2 3\n".
Complexity. Time O(n^2), space O(n) extra per row. For n = 0 the outer range is empty and the result is "".
Edge cases and pitfalls
n = 0. All methods return""; every loop or stream is empty.n = 1. Just"1\n", no separator.- Two-digit numbers. For
n ≥ 10the rows contain tokens like10and11. The tests check thatnestedLoops(11)ends with"1 2 3 4 5 6 7 8 9 10 11\n". Any approach that assumes one character per cell (for example by computing row widths as2i - 1) breaks here. charversusint.sb.append((char) j)appends the character with code pointj, an unprintable control character for smallj.sb.append((char) ('0' + j))works only forj ≤ 9. Usesb.append(j)with anint.- Method references on
IntStream.mapToObj(Integer::toString)does not compile becauseInteger.toString(int)and the instance methodtoString()are both applicable. Write the lambdaj -> Integer.toString(j)or useString.valueOf. - Trailing space. Emit the separator before every number except the first, or use
Collectors.joining(" "), which handles it for you. printlnversusprint. The returned string already ends in\n.