/* 문제 */ 재귀적인 패턴으로 별을 찍어 보자. N이 3의 거듭제곱(3, 9, 27, ...)이라고 할 때, 크기 N의 패턴은 N×N 정사각형 모양이다. 크기 3의 패턴은 가운데에 공백이 있고, 가운데를 제외한 모든 칸에 별이 하나씩 있는 패턴이다. N이 3보다 클 경우, 크기 N의 패턴은 공백으로 채워진 가운데의 (N/3)×(N/3) 정사각형을 크기 N/3의 패턴으로 둘러싼 형태이다. 예를 들어 크기 27의 패턴은 다음과 같다.
처음에는 각 단계에서 이전 단계의 결과물이 새로운 패턴 한 칸이 된다는 점을 이용해 문제를 풀려고 했다. 크기가 3인 리스트를 선언하고 각 요소를 첫째 줄, 둘째 줄, 셋째 줄로 설정해 계산한 후 마지막에 출력하는 방식이었다. 첫째 줄과 셋째 줄은 이전 단계의 결과물을 3번 반복한 패턴이고, 둘째 줄은 이전 단계의 결과물 + (N/3)*(N/3) 크기의 공백 + 이전 단계의 결과물이다. 그러나 개행 문자를 포함한 패턴을 반복하여 저장하는 데 어려움이 있었기 때문에 제대로 된 결과물이 나오지 않았다.
그래서 선택한 방법이 출력 문자 하나하나의 위치를 좌표처럼 설정해 "*"가 출력될 지 " "가 출력될 지 결정하도록 하는 방식이었다.
const fs = require("fs");
const input = fs.readFileSync("./dev/stdin").toString().trim();
const n = parseInt(input);
let res = "";
const star = (i, j, num) => {
if (i % 3 === 1 && j % 3 === 1) res += " ";
else {
if (num === 1) res += "*";
else star(parseInt(i / 3), parseInt(j / 3), parseInt(num / 3));
}
};
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
star(i, j, n);
}
res += "\n";
}
console.log(res);
우선 for 루프를 N * N번 돌며 각 좌표마다 *인지 공백인지를 결정하는 함수 star()를 호출한다. star()에서는 전달받은 좌표에 따라 문자를 하나씩 res에 더해주기 때문에, 좌표에 맞게 차례대로 문자가 저장된다. i가 x좌표, j가 y좌표를 의미한다고 볼 수 있기 때문에 한 줄이 끝나면 개행 문자를 더해 줄바꿈을 해준다.
star() 함수를 하나하나 살펴보자.
if (i % 3 === 1 && j % 3 === 1) res += " ";
기본적으로 i%3 === 1이고 j%3 === 1이면 공백이 출력된다. 3*3 크기의 정사각형에서 가운데 한 칸이 비어있는 패턴이 이 문제의 기본 단위인 것이다.
else {
if (num === 1) res += "*";
else star(parseInt(i / 3), parseInt(j / 3), parseInt(num / 3));
}
위에서 설명한 기본 공백이 아니라면 "*"를 출력해야 할 것이다. 그러나 3보다 큰 N을 처리하기 위해서는 추가로 공백을 처리해 줘야 한다. 이 부분을 재귀함수로 처리하는데, 패턴을 크게 보면 결국 가운데 한 칸이 비어있는 정사각형 패턴의 반복이다. 즉 N번째 단계에서는 N*N 크기의 정사각형에서 가운데 (N/3)*(N/3) 칸이 비어있는 패턴이 나타나야 한다. 따라서 현재 star()에서 처리하고 있는 값들을 3으로 나누어 스케일을 줄여주는 과정을 반복한다. N이 1이 되면 최소한으로 쪼개진 것이므로 "*"를 출력한다. 이 최소 단위의 공백은 위의 if문에서 처리된다.
예를 들어 N이 9일 때
이 그림에서 빨간 부분에 해당하는 좌표는
(3,3)(4,3)(5,3) (3,4)(4,4)(5,4) (3,5)(4,5)(5,5)
인데, 모두 star(1,1,3)이 호출되어 공백이 출력된다.
같은 원리로 N이 27일 때 (9,9)에 해당하는 좌표에서는 star(3,3,9)가 호출되며, 다시 star(1,1,3)이 호출되어 공백이 출력된다.