Topics › Basics › Build-up Logical Thinking
Hard
Java source ↗ Edit this article ↗
Jump to code:
What this covers
The hard pattern-printing problems (patterns 12 to 22 on the sheet) are not harder because the loops are deeper. They are harder because each row has more than one kind of segment, because the symbol printed may depend on a counter that survives across rows, because letters replace numbers, and because some shapes are hollow or mirrored. This article takes each of those ideas in turn, with one or two patterns per idea, and shows that the procedure from the easy patterns still applies: count the rows, describe row i as segments, write the length formula for each segment, and check it on the first and last rows.
As in the companion article, every pattern is returned as a String whose lines end in \n, so the Java file can test exact output.
Three segments per row: the number crown
Pattern 12 prints 1..i, then a gap, then i..1 on row i. The width must stay constant, so the gap shrinks as the number blocks grow: each block has i digits, the full width is 2n, and the gap is 2(n - i) spaces. Three segments, three pieces of code per row.
static String numberCrown(int n) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) sb.append(j);
sb.append(" ".repeat(2 * (n - i)));
for (int j = i; j >= 1; j--) sb.append(j);
sb.append('\n');
}
return sb.toString();
}
The right block counts down, which is simply a loop with j--. Row n has a gap of zero and the two blocks touch: 123321 for n = 3.
A counter that runs across rows
Pattern 13 prints 1, then 2 3, then 4 5 6. The value printed is not a function of i and j alone (it could be computed as i(i - 1)/2 + j, but nobody writes that); it is “the next number”, so keep a counter outside both loops and increment it every time you print.
static String increasingNumberTriangle(int n) {
StringBuilder sb = new StringBuilder();
int next = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
if (j > 1) sb.append(' ');
sb.append(next++);
}
sb.append('\n');
}
return sb.toString();
}
next++ appends the current value and then increments. The last number printed is n(n + 1) / 2, which the test for n = 5 confirms with 15.
Letters are numbers
A char holds a code, and 'A' + k is the code of the k-th letter after A. The addition produces an int, so a cast back to char is needed before appending, or the number would be printed. Pattern 14 prints A, AB, ABC:
static String increasingLetterTriangle(int n) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) sb.append((char) ('A' + j));
sb.append('\n');
}
return sb.toString();
}
The inner loop is 0-based here because 'A' + 0 is A; mixing 1-based rows with 0-based letter offsets is fine as long as each formula is checked. Pattern 15 is the flipped version, with the same n - i + 1 substitution used for the inverted triangles:
static String reverseLetterTriangle(int n) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= n; i++) {
for (int j = 0; j < n - i + 1; j++) sb.append((char) ('A' + j));
sb.append('\n');
}
return sb.toString();
}
Pattern 16 repeats one letter per row, the i-th letter i times. Compute the letter once per row, outside the inner loop:
static String alphaRamp(int n) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= n; i++) {
char letter = (char) ('A' + i - 1);
for (int j = 1; j <= i; j++) sb.append(letter);
sb.append('\n');
}
return sb.toString();
}
Rising then falling within a row
Pattern 17 is a centred hill: row i is n - i spaces followed by A B ... letter i ... B A, which is 2i - 1 letters. Index the letters with j from 0 to 2i - 2; while j < i the offset is j, afterwards it is 2i - 2 - j, which counts back down to 0. Check for i = 3: j = 0, 1, 2, 3, 4 gives offsets 0, 1, 2, 1, 0, that is ABCBA.
static String alphaHill(int n) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= n; i++) {
sb.append(" ".repeat(n - i));
for (int j = 0; j < 2 * i - 1; j++) {
int k = j < i ? j : 2 * i - 2 - j;
sb.append((char) ('A' + k));
}
sb.append('\n');
}
return sb.toString();
}
Pattern 18 prints the last i letters of the first n: E, then DE, then CDE for n = 5. The start offset is n - i and the end is n - 1:
static String alphaTriangle(int n) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= n; i++) {
for (int j = n - i; j < n; j++) sb.append((char) ('A' + j));
sb.append('\n');
}
return sb.toString();
}
Mirrored halves and the fold
Pattern 19, the symmetric void, is a hollow diamond cut out of a 2n-row block. In the top half, row i has n - i + 1 stars on each side and 2(i - 1) spaces between; the bottom half is the top half reversed. Two loops, one per half, are clearer than a single loop with a case split:
static String symmetricVoid(int n) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= n; i++) {
int stars = n - i + 1;
sb.append("*".repeat(stars)).append(" ".repeat(2 * (i - 1))).append("*".repeat(stars)).append('\n');
}
for (int i = 1; i <= n; i++) {
sb.append("*".repeat(i)).append(" ".repeat(2 * (n - i))).append("*".repeat(i)).append('\n');
}
return sb.toString();
}
Pattern 20, the butterfly, has 2n - 1 rows whose star count rises to n and falls again. Instead of two loops, fold the row index: i = row while row <= n, otherwise i = 2n - row. Then every row is i stars, 2(n - i) spaces, i stars:
static String butterfly(int n) {
StringBuilder sb = new StringBuilder();
for (int row = 1; row <= 2 * n - 1; row++) {
int i = row <= n ? row : 2 * n - row;
sb.append("*".repeat(i)).append(" ".repeat(2 * (n - i))).append("*".repeat(i)).append('\n');
}
return sb.toString();
}
Both techniques work for both patterns; pick whichever makes the row formula simplest.
Hollow shapes: test for the border
Pattern 21 is a hollow rectangle. Think of it as a full n × n square in which a cell is a star only on the border. For whole rows the test is simple: rows 1 and n are all stars; every other row is a star, n - 2 spaces, a star.
static String hollowRectangle(int n) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= n; i++) {
if (i == 1 || i == n) {
sb.append("*".repeat(n));
} else {
sb.append('*').append(" ".repeat(n - 2)).append('*');
}
sb.append('\n');
}
return sb.toString();
}
For n = 1 the single row is both first and last, so the else branch, with its n - 2 = -1, is never reached. Always confirm that tiny inputs stay in the safe branch.
Distance to the nearest edge
Pattern 22 draws concentric squares of numbers: n on the outer ring, n - 1 on the next, down to 1 in the centre of a (2n - 1) × (2n - 1) grid. The cleanest formula treats each cell independently. The ring a cell belongs to is its distance to the nearest edge, min(r, c, size - 1 - r, size - 1 - c) with 0-based indices, and the value printed is n minus that distance.
static String concentricSquares(int n) {
StringBuilder sb = new StringBuilder();
int size = 2 * n - 1;
for (int r = 0; r < size; r++) {
for (int c = 0; c < size; c++) {
int edge = Math.min(Math.min(r, c), Math.min(size - 1 - r, size - 1 - c));
if (c > 0) sb.append(' ');
sb.append(n - edge);
}
sb.append('\n');
}
return sb.toString();
}
This per-cell view, “what value belongs at (r, c)?”, is the most general pattern technique of all. Any shape can be printed this way with a double loop over the full grid and a formula or condition per cell; the row-by-segment approach is just an optimisation of it for shapes whose rows are simple runs.
Edge cases and pitfalls
n = 0andn = 1. Every method returns an empty string for0and one or two lines for1. Check that norepeatreceives a negative count on the inputs you accept.- Casting to char.
'A' + jis anint; forgetting the(char)cast prints65instead ofA. - Counters across rows. Reset per-row state (like the
bitin the binary triangle) inside the outer loop; keep cross-row state (likenext) outside both loops. - Width consistency. In crown, void and butterfly rows, the three segments must sum to the same width on every row; derive the gap from the width rather than guessing it.
- Fold formula.
2n - rowfor the falling half givesnatrow = nand1atrow = 2n - 1; off-by-one errors here produce a duplicated middle row. - Trailing spaces. Emit separators before elements with a guard, and avoid trailing-space segments; judges often compare output exactly.
- Hollow shapes on small inputs. Make sure the border test catches the case where first and last row coincide.
Related problems
- Easy and Medium covers the first eleven patterns and the four-question procedure used throughout this article.
- Pattern 12, Pattern 19, Pattern 20 and Pattern 22 are standalone versions of the crown, void, butterfly and concentric squares.
- For loops explains the nested loops and labeled control flow these patterns are built from.
- Cpp Basics covers the char arithmetic behind the letter patterns.