Topics › Basics › Patterns

Pattern 4

Easy TUF ID 417 A2Z

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.

  1. For i from 1 to n:
  2. For j from 1 to i: if j > 1 append a space; append the number i.
  3. Append \n.
  4. 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.

  1. For i from 1 to n: token = Integer.toString(i).
  2. Append String.join(" ", Collections.nCopies(i, token)) and then \n.
  3. 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.

  1. IntStream.rangeClosed(1, n).
  2. mapToObj(i -> String.join(" ", Collections.nCopies(i, Integer.toString(i))) + "\n").
  3. 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