Pattern 4
Java source ↗ Edit this article ↗
Jump to code:
Problem
Given an integer n, print a right-angled number pyramid in which every cell of row i (counting from 1) holds the number i: the first row is 1, the second row is 2 2, the third row is 3 3 3, and so on up to row n, which contains n written n times. Numbers in a row are separated by single spaces.
Output format: every row ends with a newline, exactly one space between numbers, no trailing space, and multi-digit numbers printed in full. n = 0 prints nothing.
Constraints: 0 ≤ n ≤ 100 is typical.
Example 1
n = 4
1
2 2
3 3 3
4 4 4 4
Row i repeats the value i exactly i times.
Example 2
n = 2
1
2 2
The second row shows both facts at once: the value is the row number and so is the count.
The one decision: which counter do you print?
Pattern 3 and Pattern 4 share the same loops: i over rows 1..n, j over columns 1..i. The only difference is the cell content. Pattern 3 prints j, the column counter, and the rows count upward. This pattern prints i, the row counter, so a row is a single value repeated. Being able to state this difference in one sentence is the skill these two exercises are testing: separate the shape (the loop bounds) from the content (the formula applied to (i, j)).
Each approach below returns the pattern as a String so the tests can compare it to expected literals, and the caller prints it once.
Approach 1: Nested loops
Intuition. Row i has i cells and each cell holds i. The inner loop only counts to i; its variable j is never printed. The separator rule is the usual one: a space before every cell except the first.
Algorithm.
- For
ifrom1ton: - For
jfrom1toi: ifj > 1append a space; append the numberi. - 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(i);
}
sb.append('\n');
}
return sb.toString();
}
Complexity. Time O(n^2): n(n+1)/2 numbers are appended. Space O(n^2) for the output, O(1) extra.
Note sb.append(i) with an int argument. StringBuilder.append(int) writes the decimal digits, so i = 12 becomes "12". Converting to a char first would be wrong for anything above 9.
Dry run for n = 3, showing what each inner iteration appends:
| i | j | appended | row so far |
|—|—|—|—|
| 1 | 1 | 1 | 1 |
| 2 | 1 | 2 | 2 |
| 2 | 2 | ` 2 | 2 2 |
| 3 | 1 | 3 | 3 |
| 3 | 2 | 3 | 3 3 |
| 3 | 3 | 3 | 3 3 3` |
The value column never changes within a row, only the separator logic does; that is the visual signature that distinguishes this pattern from the counting pyramid.
Approach 2: Repeat one token per row
Intuition. Since a row is one token repeated i times, build the token once (Integer.toString(i)), make i copies with Collections.nCopies, and glue them with String.join(" ", ...), which places a space between elements and never at the end. The inner loop is replaced by two library calls that say exactly what the row is.
Algorithm.
- For
ifrom1ton:token = Integer.toString(i). - Append
String.join(" ", Collections.nCopies(i, token))and then\n. - Return the string.
static String repeatToken(int n) {
StringBuilder out = new StringBuilder();
for (int i = 1; i <= n; i++) {
String token = Integer.toString(i);
out.append(String.join(" ", Collections.nCopies(i, token))).append('\n');
}
return out.toString();
}
Complexity. Time O(n^2): join touches every character of every row once. Space O(n) extra for one row’s list and string at a time.
Collections.nCopies does not actually allocate i separate strings; it returns an immutable list view that reports the same element i times, so there is no hidden cost beyond the join.
Approach 3: Stream pipeline
Intuition. Pattern 4 is “for each i in 1..n, the row i i ... i”, and that reads naturally as a stream: IntStream.rangeClosed(1, n) for the rows, mapToObj to build each row string, Collectors.joining() to concatenate. It is the same computation as Approach 2 expressed declaratively.
Algorithm.
IntStream.rangeClosed(1, n).mapToObj(i -> String.join(" ", Collections.nCopies(i, Integer.toString(i))) + "\n").collect(Collectors.joining()).
static String streams(int n) {
return IntStream.rangeClosed(1, n)
.mapToObj(i -> String.join(" ", Collections.nCopies(i, Integer.toString(i))) + "\n")
.collect(Collectors.joining());
}
Dry run for n = 3:
| i | token | nCopies(i, token) |
row |
|---|---|---|---|
| 1 | "1" |
["1"] |
"1\n" |
| 2 | "2" |
["2", "2"] |
"2 2\n" |
| 3 | "3" |
["3", "3", "3"] |
"3 3 3\n" |
Result: "1\n2 2\n3 3 3\n".
Complexity. Time O(n^2), space O(n) extra. For n = 0 the stream is empty and the result is "".
Edge cases and pitfalls
n = 0. Every approach returns"".n = 1. A single"1\n".- Two-digit values. Row 10 is
10 10 10 10 10 10 10 10 10 10. The tests assert exactly that, so any one-character-per-cell assumption fails. Widths are not2i - 1oncei ≥ 10. - Printing the wrong counter. Printing
jgives Pattern 3. If your output counts upward within a row, you printed the inner variable by mistake. chararithmetic.(char) ('0' + i)works only for single digits. Keep the value asintand letappendorInteger.toStringformat it.- Trailing space. Emit the separator before every token except the first, or let
String.joinhandle it. printlnversusprint. The returned string already ends with\n;printlnwould add a blank line.- Immutable
nCopieslist. It cannot be modified. That is fine here becausejoinonly reads it, butaddon it throwsUnsupportedOperationException.