Pattern 12
Java source ↗ Edit this article ↗
Jump to code:
Problem
Given a positive integer n, print the number crown: n rows, each 2n characters wide. Row i (counting from 1) prints the numbers 1, 2, ..., i on the left, the numbers i, ..., 2, 1 on the right, and fills the middle with spaces so that the two halves move towards each other and finally meet in the last row.
Constraints: 1 ≤ n ≤ 9 in the classic version, so that every number is a single character and the rows stay aligned. The code below also works for larger n, but the picture skews once two-digit numbers appear.
Example 1
n = 3
1 1
12 21
123321
Row 1 has one number on each side and four spaces between; row 3 has no spaces left.
Example 2
n = 5
1 1
12 21
123 321
1234 4321
1234554321
Each row is 10 characters wide; the gap shrinks by two characters per row.
Reading the pattern
Every printing pattern becomes easy once you can describe one row with a formula in i and n. Look at the anatomy of a row here: three parts, left to right.
| part | content | length |
|---|---|---|
| left half | 1 2 ... i |
i |
| gap | spaces | 2(n - i) |
| right half | i ... 2 1 |
i |
The three lengths add up to i + 2(n - i) + i = 2n, which is why every row has the same width. The gap starts at 2(n - 1) in row 1 and shrinks by exactly two per row, because each new row adds one character on the left and one on the right. That “shrinks by two” is the detail people get wrong most often; n - i spaces would give a lopsided crown.
For n = 5 the parts are:
| i | left | gap | right |
|---|---|---|---|
| 1 | 1 |
8 | 1 |
| 2 | 12 |
6 | 21 |
| 3 | 123 |
4 | 321 |
| 4 | 1234 |
2 | 4321 |
| 5 | 12345 |
0 | 54321 |
Both approaches below return the finished picture as a single String (rows separated by \n) instead of printing directly. That makes the result testable: main compares the string against hand-written expectations and only then prints it.
Approach 1: Three loops per row
Intuition. Translate the row anatomy literally. An outer loop walks the rows; inside it, one loop prints the ascending numbers, one prints the spaces, one prints the descending numbers. Nothing is remembered between rows, so every row is computed from scratch using only i and n.
Algorithm.
- For
ifrom1ton: - Append
jforj = 1 .. i. - Append
2(n - i)spaces. - Append
jforj = i .. 1. - End the row with a newline.
static String nestedLoops(int n) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) sb.append(j); // 1 2 ... i
for (int j = 0; j < 2 * (n - i); j++) sb.append(' '); // shrinking gap
for (int j = i; j >= 1; j--) sb.append(j); // i ... 2 1
sb.append('\n');
}
return sb.toString();
}
Complexity. Time O(n^2): n rows of 2n characters each, and every character costs O(1) to append. Space O(n^2) for the returned string, which is unavoidable because that is the size of the output; no extra working memory beyond the builder.
Note that sb.append(j) with an int argument appends the decimal text of j ("12" for twelve). sb.append((char) j) would append the character with code point j, an invisible control character, which is a classic mix-up.
Approach 2: Grow the two halves incrementally
Intuition. The left half of row i is the left half of row i - 1 with i appended. The right half of row i is the right half of row i - 1 with i prepended. So instead of rebuilding both halves from 1 each time, keep them in two StringBuilders that grow by one number per row, and glue left + gap + right together. The gap is a single call to " ".repeat(2 * (n - i)).
This is the same work asymptotically, but it changes the way you think: a row is “the previous row, slightly extended”, a viewpoint that pays off in harder patterns where recomputation is expensive.
Algorithm.
- Keep
left = ""andright = "". - For
ifrom1ton: appenditoleft, insertiat position 0 ofright. - Append
left, then2(n - i)spaces, thenright, then a newline.
static String incremental(int n) {
StringBuilder sb = new StringBuilder();
StringBuilder left = new StringBuilder(); // "12...i"
StringBuilder right = new StringBuilder(); // "i...21"
for (int i = 1; i <= n; i++) {
left.append(i);
right.insert(0, i);
sb.append(left).append(" ".repeat(2 * (n - i))).append(right).append('\n');
}
return sb.toString();
}
Dry run for n = 4:
| i | left | gap | right | row |
|---|---|---|---|---|
| 1 | 1 |
6 | 1 |
1 1 |
| 2 | 12 |
4 | 21 |
12 21 |
| 3 | 123 |
2 | 321 |
123 321 |
| 4 | 1234 |
0 | 4321 |
12344321 |
Complexity. Time O(n^2). insert(0, i) shifts the existing characters, so it costs O(i), and copying left and right into the output also costs O(i) per row; summed over the rows that is O(n^2), the same as the output size. Space O(n^2) for the output plus O(n) for the two halves.
Why does prepending give the reverse order for free? Because the sequence i, i-1, ..., 1 is exactly what you get when each new number lands in front of the earlier ones. For two-digit numbers this is also more robust than reversing a string: reversing "...910" would produce "019...", while prepending 10 keeps it intact.
Edge cases and pitfalls
n = 0. No rows; both methods return the empty string. The tests check this explicitly because an off-by-one in the row loop usually shows up here first.n = 1. A single row11with a gap of zero. The gap formula2(n - i)must evaluate to0, not go negative;" ".repeat(0)is fine, but" ".repeat(-1)throwsIllegalArgumentException.- Gap shrinks by two, not one. Writing
n - ispaces produces rows of different widths. Compute the widthi + gap + ifor one row to catch this before running anything. - Two-digit numbers (
n ≥ 10). The gap counts characters assuming each number is one character wide. From10onwards the left and right halves grow by two characters per row while the gap still shrinks by two, so the crown skews. The two approaches still agree with each other (the tests checkn = 10..12), but the picture is no longer symmetric. Pad each number to a fixed width if you needn > 9to look right. append(int)versusappend(char).sb.append(5)appends'5';sb.append((char) 5)appends an unprintable control character. Casting aninttocharonly makes sense with an offset such as(char) ('0' + 5).- Printing the result. The string already ends with a newline after every row, so print it with
System.out.print, notprintln, or you get an extra blank line. - String concatenation in a loop.
row = row + jcreates a newStringon every iteration; for a pattern this small it does not matter, butStringBuilderis the habit worth forming.
Related problems
- Pattern 13 continues with number triangles, where the numbers keep counting across rows instead of restarting.
- Pattern 17 is another mirror pattern, with letters that climb and descend inside one centred row.
- Pattern 19 and Pattern 20 use exactly the same “gap shrinks by two” arithmetic with stars instead of numbers.