반응형
Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
Tags
- java
- 둘만의 암호 자바
- Arrays
- 자바의 정석
- greedy
- 프로그래머스
- 오버라이딩
- localtime
- 리눅스
- 둘만의 암호
- Comparator
- LocalDate
- SQL Mapper
- spring security 설정
- BFS
- over()
- 혼공얄코
- hackerrank
- 백트래킹
- 프로그래머스 둘만의 암호
- 분할정복
- 티스토리챌린지
- 자바의정석
- 분할정복 알고리즘
- spring security
- 멀티프로세싱
- Comparable
- 오블완
- 멀티태스킹
- 오버로딩
Archives
- Today
- Total
쉽게 쉽게
[알고리즘] 백트래킹(Backtracking)이란? (백준 15652, 9663) 본문
반응형
📌 핵심 요약
- 백트래킹 — 선택 → 재귀 → 취소를 반복하며 모든 경우를 탐색하되, 조건 불만족 시 가지치기(Pruning)하는 기법
- N과 M (4) · 백준 15652 — 중복을 허용하는 비내림차순 수열,
dfs(depth+1, i)로 같은 수 재선택 허용 - N-Queen · 백준 9663 — 1차원 배열
queen[row]=col로 퀸 위치 관리, 열·대각선 충돌을isValid()로 가지치기
1 백트래킹이란?
백트래킹(Backtracking)은 모든 경우의 수를 탐색하되, 불필요한 경우는 미리 제거(가지치기)하는 알고리즘 기법입니다.
완전 탐색(Brute Force)에 가지치기를 더한 것이라고 생각하면 됩니다.
핵심 동작 원리
백트래킹은 다음 세 단계를 반복합니다.
- 선택(Choose) — 현재 상태에서 가능한 선택지 중 하나를 고른다
- 재귀(Explore) — 선택한 상태로 다음 단계를 재귀 탐색한다
- 취소(Unchoose) — 탐색이 끝나면 선택을 되돌리고 다른 선택지를 시도한다
가지치기(Pruning)란?
현재 상태에서 더 이상 유효한 답이 나올 수 없다고 판단되면 해당 가지를 탐색하지 않고 즉시 되돌아오는 것입니다. 이를 통해 탐색 공간을 획기적으로 줄일 수 있습니다.일반적인 코드 구조
static void dfs(int depth) {
// 종료 조건: 원하는 깊이에 도달하면 결과 처리
if (depth == target) {
process();
return;
}
for (int i = 0; i < candidates; i++) {
if (!isValid(i)) continue; // 가지치기
choose(i); // 선택
dfs(depth + 1); // 재귀
unchoose(i); // 취소 (백트래킹)
}
}
2백준 15652 — N과 M (4) 풀이
백준 #15652
N과 M (4)
1부터 N까지 자연수 중에서 M개를 고른 수열을 출력합니다. 같은 수를 여러 번 골라도 되며, 고른 수열은 비내림차순이어야 합니다. (앞 수 ≤ 뒷 수)
풀이 과정
- 중복을 허용하므로
visited[]는 필요 없습니다. - 비내림차순을 위해
start파라미터로 현재 선택한 숫자 이상만 다음에 고를 수 있게 합니다. dfs(depth+1, i+1)이 아닌dfs(depth+1, i)로 넘겨 같은 수의 재선택을 허용합니다.- depth == M이 되면
arr[]에 담긴 수열을 출력합니다.
전체 코드
import java.io.*;
import java.util.*;
public class Main {
static int n, m;
static int[] arr;
static StringBuilder sb = new StringBuilder();
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
n = Integer.parseInt(st.nextToken());
m = Integer.parseInt(st.nextToken());
arr = new int[m];
dfs(0, 1);
System.out.print(sb);
}
static void dfs(int depth, int start) {
if (depth == m) {
for (int i = 0; i < m; i++) {
sb.append(arr[i]);
if (i < m - 1) sb.append(" ");
}
sb.append("\n");
return;
}
for (int i = start; i <= n; i++) {
arr[depth] = i;
dfs(depth + 1, i); // i+1이 아닌 i → 같은 수 재선택 허용
}
}
}
동작 흐름 (N=3, M=2)
dfs(0, 1)
├─ i=1 → dfs(1, 1)
│ ├─ i=1 → 출력 "1 1" ✅ 같은 수 허용
│ ├─ i=2 → 출력 "1 2"
│ └─ i=3 → 출력 "1 3"
├─ i=2 → dfs(1, 2)
│ ├─ i=2 → 출력 "2 2" ✅ 같은 수 허용
│ └─ i=3 → 출력 "2 3"
└─ i=3 → dfs(1, 3)
└─ i=3 → 출력 "3 3" ✅ 같은 수 허용
i+1 vs i — 한 글자 차이의 큰 의미
dfs(depth+1, i+1)은 현재 고른 수보다 큰 수만 다음에 선택 가능 (오름차순, 15650).dfs(depth+1, i)는 현재 고른 수와 같거나 큰 수를 선택 가능 (비내림차순, 15652).3백준 9663 — N-Queen 풀이
백 #9663
N-Queen
N×N 크기의 체스판에 N개의 퀸을 서로 공격하지 못하게 배치하는 경우의 수를 구합니다. 퀸은 가로, 세로, 대각선 모든 방향을 공격합니다.
풀이 과정
- N개의 퀸을 N×N에 배치하면 반드시 각 행에 퀸이 하나씩 있어야 합니다. 따라서
dfs(row)로 행 단위로 탐색합니다. queen[row] = col로 row행에 col열에 퀸을 배치합니다. 1차원 배열로 관리하면 코드가 훨씬 간결해집니다.isValid(row, col)에서 같은 열과 대각선 충돌을 검사해 가지치기합니다.row == N이 되면 N개를 모두 배치한 것이므로count++합니다.
충돌 체크 조건
현재 (row, col)에 퀸을 놓으려 할 때, 이전 행의 퀸 (i, queen[i])와 비교합니다.
| 체크 항목 | 조건 | 이유 |
|---|---|---|
| 같은 행 | 자동 방지 | 행 단위로 내려가므로 같은 행에 2개 배치 불가 |
| 같은 열 | queen[i] == col |
이전 퀸과 같은 열이면 충돌 |
| 대각선 | Math.abs(queen[i]-col) == Math.abs(i-row) |
행 차이 == 열 차이 이면 대각선 |
대각선 공식이 왜 행 차이 == 열 차이 인가요?
체스판에서 대각선 이동은 항상 행이 n 움직이면 열도 n 움직입니다.예) (0,1) → (1,2) → (2,3) : 행 +1, 열 +1 → 차이 항상 같음
따라서 두 점의 행 차이와 열 차이가 같으면 반드시 대각선 관계입니다.
Math.abs()를 쓰는 이유는 오른쪽 아래 / 왼쪽 아래 두 방향 모두를 잡기 위해서입니다.전체 코드
import java.util.*;
public class Main {
static int n;
static int[] queen;
static int count = 0;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
queen = new int[n];
dfs(0);
System.out.println(count);
}
static void dfs(int row) {
if (row == n) {
count++;
return;
}
for (int col = 0; col < n; col++) {
if (isValid(row, col)) {
queen[row] = col;
dfs(row + 1);
// queen[row] 복구 불필요 — 다음 col에서 덮어쓰기 때문
}
}
}
static boolean isValid(int row, int col) {
for (int i = 0; i < row; i++) {
// 같은 열 체크
if (queen[i] == col) return false;
// 대각선 체크: 행 차이 == 열 차이
if (Math.abs(queen[i] - col) == Math.abs(i - row)) return false;
}
return true;
}
}
동작 흐름 (N=4 일부)
dfs(0) → 0행에 퀸 놓기
├─ col=0 → queen[0]=0
│ dfs(1)
│ ├─ col=0 → 같은 열 ❌
│ ├─ col=1 → 대각선 ❌
│ ├─ col=2 → queen[1]=2
│ │ dfs(2)
│ │ ├─ col=1 → queen[2]=1
│ │ │ dfs(3) → col=3 → queen[3]=3 → count++ ✅
│ │ └─ col=3 → queen[2]=3
│ │ dfs(3) → 모두 충돌 ❌
│ └─ col=3 → queen[1]=3 ...
└─ col=1 → queen[0]=1 ...
4 두 문제로 보는 백트래킹 비교
| 항목 | N과 M (4) · 15652 | N-Queen · 9663 |
|---|---|---|
| 탐색 단위 | depth (고를 자리) | row (행 번호) |
| 가지치기 조건 | start 파라미터로 범위 제한 | isValid() — 열·대각선 충돌 체크 |
| 상태 저장 | int[] arr |
int[] queen |
| 백트래킹 방식 | 다음 for 루프에서 덮어쓰기 | 다음 for 루프에서 덮어쓰기 |
| 출력/결과 | 수열 자체를 출력 | 경우의 수(count)만 출력 |
백트래킹 문제 접근 체크리스트
① 탐색 단위(depth/row)를 무엇으로 잡을지 결정한다② 종료 조건(depth == M, row == N)을 명확히 한다
③ 가지치기 조건(visited, isValid, start)을 설계한다
④ 상태 복구(visited=false, 덮어쓰기)가 필요한지 확인한다
| 잘못된 내용이 있다면 지적부탁드립니다. 방문해주셔서 감사합니다. |

반응형
'알고리즘 > 완전 탐색 및 백트래킹' 카테고리의 다른 글
| [프로그래머스] 문자열 압축 -Java (0) | 2026.02.21 |
|---|---|
| [프로그래머스] 메뉴 리뉴얼 -Java (1) | 2026.01.22 |