문제에서 요구하는 것은 같은 색 점을 이어 사이클이 존재하는지 여부를 판별하는 것
N x M 의 격자판이 주어짐👉 출력 : 사이클이 존재하면 Yes, 존재하지 않으면 No
DFS 탐색 활용
사이클 판별 조건
4 이상이면 사이클이 존재한다고 판단최적화
visited[][] 배열 사용 변수
n, m : 격자 크기arr : 문자 배열visited : 방문 체크 배열dx, dy : 상하좌우 이동 좌표foundCycle : 사이클 발견 여부흐름
Yes / No 출력 사용 알고리즘
해당 알고리즘을 선택한 근거
사이클 탐지 문제에 적합
보드 크기가 작음
50 x 50 = 2500칸.O(N*M*4) 정도로 충분히 해결 가능
package Baekjoon;
import java.util.Scanner;
public class B_16929_TwoDots {
static int n, m;
static char[][] arr;
static boolean[][] visited;
static int[] dx = {-1,1,0,0};
static int[] dy = {0,0,-1,1};
static boolean foundCycle;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//입력
n = sc.nextInt();
m = sc.nextInt();
arr = new char[n][m];
visited = new boolean[n][m];
for (int i = 0; i < n; i++) {
String line = sc.next();
for (int j = 0; j < m; j++) {
arr[i][j] = line.charAt(j);
}
}
//로직
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if(!visited[i][j]) dfs(i, j, -1, -1, 1);
if (foundCycle) break;
}
}
//출력
System.out.println(foundCycle ? "Yes" : "No");
}
//fromX , fromY 부모 좌표 / depth 현재까지 이동한 칸 수
private static void dfs(int x, int y, int fromX, int fromY, int depth) {
visited[x][y] = true; //방문처리
for (int h = 0; h < 4; h++) {
int nx = x + dx[h];
int ny = y + dy[h];
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue; //격자 범위 벗어나면 continue
if (arr[x][y] != arr[nx][ny]) continue; //같은 알파벳이 아니면 continue
if (nx == fromX && ny == fromY) continue; //바로 이전 칸으로 돌아가려는 경우 continue
//방문처리
if (!visited[nx][ny]) {
//같은 알파벳이고, 아직 방문하지 않았으면
dfs(nx, ny, x, y, depth+1);
} else {
//이미 방문했고(visited==true), depth >= 4라면 사이클 발견
// depth가 4 이상 일 때 사이클 인 이유는 사이클이 만들어지려면 최소 4개의 점이 필요하기 때문
if (depth >= 4) {
foundCycle = true;
return;
}
}
if (foundCycle) break;
}
}
}
// fromX, fromY : 바로 이전 좌표 / depth : 현재까지 이동한 칸 수
private static void dfs(int x, int y, int fromX, int fromY, int depth) {
visited[x][y] = true; //방문처리
for (int h = 0; h < 4; h++) {
int nx = x + dx[h];
int ny = y + dy[h];
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue; //격자 범위 벗어나면 continue
if (arr[x][y] != arr[nx][ny]) continue; //같은 알파벳이 아니면 continue
if (nx == fromX && ny == fromY) continue; //바로 이전 칸으로 돌아가려는 경우 continue
if (!visited[nx][ny]) {
//같은 알파벳이고, 아직 방문하지 않았으면
dfs(nx, ny, x, y, depth+1);
} else {
//이미 방문했고(visited==true), depth >= 4라면 사이클 발견
if (depth >= 4) {
foundCycle = true;
return;
}
}
if (foundCycle) break;
}
}
현재 좌표
(x, y)에서 같은 알파벳을 따라 인접 칸을 탐색하며, 사이클(길이 ≥ 4) 이 존재하는지 판별
사이클을 발견하면 전역 플래그foundCycle을true로 설정하고 탐색 종료
x, y : 현재 탐색 중인 좌표fromX, fromY : 바로 이전(부모) 좌표 - 부모로 되돌아가는 경우를 판별하기 위해 필요depth : 시작점에서 지금까지 이동해 온 칸 수 (시작 호출 시 1로 시작)현재 노드 방문 처리
visited[x][y] = true;
visited[x][y] = true; 로 현재 칸을 방문 처리상하좌우 4방향 반복
(nx, ny) 계산범위 검사
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
nx, ny가 격자 범위를 벗어나면 continue같은 알파벳 검사
if (arr[x][y] != arr[nx][ny]) continue;
arr[x][y] != arr[nx][ny] 으로 같은 알파벳이 아니면 그 방향은 탐색 대상 아님 → continue부모(직전 칸) 검사
if (nx == fromX && ny == fromY) continue;
nx == fromX && ny == fromY 인 경우는 바로 직전 칸(부모 칸)으로 되돌아가려는 동작이므로 무시 → continue미방문이면 재귀 호출
!visited[nx][ny] 이면 dfs(nx, ny, x, y, depth+1) 로 깊이 증가시키며 재귀 탐색이미 방문된 칸을 만난 경우(사이클 후보)
visited[nx][ny] == true 이면 그 칸은 이미 같은 연결 성분에서 방문된 칸
이때 depth >= 4 라면 현재 경로가 최소 4칸 이상이며, 부모가 아닌 다른 방문 지점을 만난 것이므로 사이클로 판단
사이클이면 foundCycle = true; 하고 return; (즉시 현재 DFS 흐름에서 탈출).
조기 종료 처리
foundCycle 이 true이면 바깥 반복문을 멈추기 위해 break (또는 return) 처리
- 명시적이지 않음
- 이 DFS는 별도의 depth == something 같은 전형적 기저 조건이 없고,
사이클 발견(visited 재방문 + depth >= 4) 이 사실상 종료 조건- 모든 분기에서 재귀가 끝나면 자연스럽게 반환되어 탐색 종료
왜 fromX/fromY가 필요한가?
⭐ 사이클 판별 조건을 depth >= 4 로 두는 이유는 사이클은 최소 4개의 칸이 필요하기 때문
foundCycle 을 전역 플래그로 두면 재귀 깊게 내려간 상태에서도 빠르게 탐색을 멈출 수 있으므로 성능상 유리
foundCycle 이 true가 되면 가능한 한 빨리 return 하고 상위 호출들도 확인 후 종료