예전에 풀었던 문제~ 그래두 확실히 하기 위해서 끄적거렸음
물구나무 서서도 재귀 문제라 쉽게 풀리겠지 생각했지만
내가 물구나무에 소질이 없어서 1시간 걸렸다.
먼저, 순회를 시작하기 전에 맨 처음 원소를 flag 변수로 잡고 시작
순회 하다가 flag랑 다른 애 나오면, 4분할을 해야 하잖아요?
위 처럼 길이가 4인 정사각형이 주어져있다고 했을 때,
현재 flag (1)
과 다른 0
을 만나면 우측 그림처럼 4분할해서 4번의 재탐색이 필요함.
4분할의 기점인 위치를 살펴보면,
위치 | 좌표 |
---|---|
좌상단 | (0,0) |
우상단 | (0,2) |
좌하단 | (2,0) |
우하단 | (2,2) |
시작점이 (len=2)
을 따른다는 사실을 알 수 있다.
이런 접근 방식을 가지고 코드를 짜면 됨
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class N_2630 {
private static int res[];
public static void recur(int [][] map, int row, int col, int len){
int flag = map[row][col];
if(len == 1){
res[flag]++;
return;
}
for(int i=row; i<row+len;i++){
for(int j=col;j<col+len;j++){
if(flag != map[i][j]){
int half = len/2;
recur(map, row,col, half);
recur(map, row,col+half, half);
recur(map, row+half,col, half);
recur(map, row+half,col+half, half);
return;
}
}
}
res[flag]++;
return;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
int [][] map = new int[n][n];
res = new int[2];
for(int i=0;i<map.length;i++){
StringTokenizer st = new StringTokenizer(br.readLine());
for(int j=0;j<map[0].length;j++){
map[i][j] = Integer.parseInt(st.nextToken());
}
}
recur(map, 0,0,n);
System.out.println(res[0]+" "+res[1]);
}
}
하나씩 살펴봅시다.
입력
public class N_2630 {
private static int res[];
...
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
int [][] map = new int[n][n];
res = new int[2];
for(int i=0;i<map.length;i++){
StringTokenizer st = new StringTokenizer(br.readLine());
for(int j=0;j<map[0].length;j++){
map[i][j] = Integer.parseInt(st.nextToken());
}
}
recur(map, 0,0,n);
System.out.println(res[0]+" "+res[1]);
}
}
n
: 초기에 주어지는 종이 크기map
: 배열 저장res
: 색종이 개수 저장2차원 배열인 map에 입력으로 주어지는 초기 색종이 값 저장
recur 함수를 호출하여 탐색
recur 함수
public class N_2630 {
private static int res[];
public static void recur(int [][] map, int row, int col, int len){
int flag = map[row][col];
if(len == 1){
res[flag]++;
return;
}
for(int i=row; i<row+len;i++){
for(int j=col;j<col+len;j++){
if(flag != map[i][j]){
int half = len/2;
recur(map, row,col, half);
recur(map, row,col+half, half);
recur(map, row+half,col, half);
recur(map, row+half,col+half, half);
return;
}
}
}
res[flag]++;
return;
}
...
recur 함수는 row
, col
매개변수에 현재 종이의 flag가 될 시작점을 받고,
len에는 현재 종이의 길이를 받음
만일 len이 1인 경우에는 한 칸만 탐색하면 되기때문에,
res 배열의 flag 위치의 값을 1을 증가.
그 외의 경우 배열을 순회하며 flag와 다른 값을 마추진 경우 4분할을 다시 시작합니다.
여기서 row, col 값을 넘겨줄 때,
현재 인덱스 (i,j)
를 넘겨주는 실수를 했는데,
그렇게 넘겨주면 내가 원하는 곳이 아닌 엉뚱한 곳을 시작점으로 4분할 하게 되므로..
꼭row, col
을 넘겨줘야한다.넘 당연한 소리 같지만, 나는 이게 발목을 좀 잡았다.
나는 이 코드의 핵심이 return
이라고 생각한다.
왜냐하면 처음에 return
이 아닌 break
를 썼다가 색종이 개수가 280개인가 웃기지도 않은 숫자가 나왔다.
이중반복문이기 때문에, break를 써도 상위 반복문(현재 i)
은 그대로 진행이 돼서
우리가 앞전에 4분할해서 탐색한 부분을 중복 탐색 하기 때문이다.
우리는 한 번 안 맞으면 더 안 볼 거잖아요?
그러려면 함수를 탈출하는 return을 작성해줘야 원하는 로직으로 프로그램이 동작합니다.
궁금하면, 디버깅 해보면 됨
해빈이 문제에서 조합이 나의 약점이라고 했는데 조합은 재귀를 활용한 문제라서
정확히는 재귀가 나의 약점이다.
이번 문제와 비슷한 문제를 3~4번 정도 풀었었는데, 1시간이나 걸리면 안됐다.
조금 더 생각하고 풀어서 돌아가는 일이 없도록 해야겠다.
그래도 풀었죠? ㅎ