Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 179 additions & 8 deletions brute-force/backtracking/backtracking.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@
* **N-Queen 문제**: 체스판에 N개의 퀸을 배치
* **스도쿠 풀이**: 빈 칸에 숫자 채우기
* **미로 찾기**: 모든 경로 탐색
* **중복 순열/조합 생성** (N과 M 시리즈)
* **알파벳 문제** (1987): 같은 알파벳을 두 번 지나지 않고 최대 경로 찾기
* **부분 집합 생성**
* **순열/조합 생성**: 주어진 집합에서 특정 개수의 순열이나 조합 생성
* **제약 조건 탐색**: 특정 조건을 만족하는 경로나 해 찾기
* **부분 집합 생성**: 주어진 집합의 모든 부분 집합 생성
* **문자열 조합 생성**: 문자열에서 특정 크기의 조합 생성

---

Expand Down Expand Up @@ -90,7 +91,10 @@ void backtrack(상태) {

➡️ 상태를 되돌리지 않으면, 한 경로의 상태가 다른 경로 탐색을 방해합니다.

> **구체적인 예시**: 알파벳 문제(1987)의 상세한 구현은 [boj-1987 문제 문서](./boj-1987/)를 참고하세요.
**사고 과정:**
- 한 경로를 탐색할 때 상태를 변경함
- 그 경로 탐색이 끝나면 상태를 원래대로 되돌려야 함
- 그래야 다른 경로를 탐색할 때 이전 경로의 영향이 없음

---

Expand Down Expand Up @@ -135,7 +139,10 @@ void dfs(정점) {
* **백트래킹**: 재귀 호출 후 상태를 되돌려서 **다른 경로를 탐색**할 수 있게 함
* **일반 DFS**: 상태를 되돌리지 않아서 **한 번 방문한 정점은 다시 방문하지 않음**

> **구체적인 예시**: 알파벳 문제(1987)와 순열 사이클 문제(10451)의 상세한 비교는 각 문제 문서를 참고하세요.
**사고 과정:**
- 백트래킹은 "모든 가능한 경로를 탐색"해야 할 때 사용
- 일반 DFS는 "그래프의 구조를 파악"할 때 사용
- 상태를 되돌리느냐 마느냐가 핵심 차이

### 왜 백트래킹은 보통 DFS로 구현되는가?

Expand All @@ -151,7 +158,7 @@ void dfs(정점) {
| **목적** | 모든 경로 탐색 (최대값, 모든 해 찾기) | 그래프 탐색 (연결 요소, 사이클) |
| **대상** | 상태 공간 트리 | 그래프 (정점, 간선) |
| **구현 방식** | DFS (재귀) | DFS (재귀/스택) |
| **사용 예시** | N-Queen, 스도쿠, 알파벳(1987) | 순열 사이클(10451), 연결 요소 |
| **사용 예시** | 모든 경로 탐색, 제약 조건 탐색 | 그래프 연결성, 사이클 탐지 |
| **핵심 특징** | 상태 되돌리기 | 깊이 우선 탐색 |

**결론**: 백트래킹은 "상태를 되돌리는 특수한 형태의 DFS"라고 이해하면 됩니다.
Expand Down Expand Up @@ -200,7 +207,10 @@ void backtrack(상태) {
* 다음 반복에서 자동으로 덮어씌워짐
* 명시적 되돌리기 불필요 (암묵적 되돌리기)

> **구체적인 예시**: 알파벳 문제(1987)와 중복 순열 문제(15651)의 상세한 구현은 각 문제 문서를 참고하세요.
**사고 과정:**
- 상태가 독립적이면 자동으로 덮어씌워지므로 되돌릴 필요 없음
- 상태가 공유되면 명시적으로 되돌려야 함
- 문제의 특성에 따라 판단해야 함

### 핵심 요소

Expand Down Expand Up @@ -278,6 +288,7 @@ sequenceDiagram
| **순열** | 각 수는 한 번만 사용 | 필요 | 불필요 | N!/(N-M)! |
| **조합** | 순서 무관, 중복 없음 | 필요 (이전 수 이후만) | 불필요 | N!/(M!(N-M)!) |
| **중복 조합** | 순서 무관, 중복 허용 | 필요 (현재 수 이후만) | 불필요 | (N+M-1)!/(M!(N-1)!) |
| **문자열 조합 생성** | 문자열에서 특정 크기의 조합 생성 | 불필요 (인덱스로 제어) | 필요 (남은 문자 부족 시) | C(n, r) |
| **제약 조건 문제** | 특정 조건 만족해야 함 | 필요 | 필요 | 문제에 따라 다름 |

### 중복 순열의 특수성
Expand Down Expand Up @@ -419,7 +430,10 @@ void backtrack(상태) {
* **패턴 1**: 배열 인덱스에 값 할당 → 다음 반복에서 자동 덮어씌워짐 → 암묵적 되돌리기
* **패턴 2**: 공유 상태(배열, 전역 변수) → 자동 덮어씌워지지 않음 → 명시적 되돌리기 필수

> **구체적인 예시**: 알파벳 문제(1987)의 상세한 구현은 [boj-1987 문제 문서](./boj-1987/)를 참고하세요.
**사고 과정:**
- 상태가 독립적인지 공유되는지 판단
- 독립적이면 자동으로 처리됨
- 공유되면 명시적으로 되돌려야 함

### 패턴 3: 명시적 상태 되돌리기 (StringBuilder 사용)

Expand Down Expand Up @@ -455,10 +469,167 @@ void backtrack(int depth) {
* 다음 반복에서 이전 값이 남아있으면 잘못된 결과가 됨
* 따라서 **명시적으로 `sb.setLength(...)`로 되돌려야** 함

### 패턴 4: 인덱스 기반 조합 생성 (문자열 조합)

```java
static void generateCombinations(
String order, int courseSize, int start,
StringBuilder current, Map<String, Integer> countMap
) {
// 종료 조건
if (current.length() == courseSize) {
countMap.put(current.toString(), ...);
return;
}

// 가지치기
if (order.length() - start < courseSize - current.length()) {
return;
}

// 선택지 탐색
for (int i = start; i < order.length(); i++) {
current.append(order.charAt(i)); // 선택
generateCombinations(order, courseSize, i + 1, current, countMap);
current.setLength(current.length() - 1); // ✅ 상태 되돌리기
}
}
```

**특징:**
* 인덱스(`start`)로 선택 가능한 범위 제어
* StringBuilder로 누적 상태 관리
* 가지치기로 불가능한 경우 조기 종료
* 인자로 모든 정보 전달 (독립적인 호출 가능)

**사고 과정:**
- 각 호출이 독립적이어야 하는지 판단
- 독립적이면 인자로 전달
- 공유되면 전역 변수 사용

**패턴 비교:**
* **패턴 1**: 배열 인덱스 → 자동 덮어씌워짐 → 암묵적 되돌리기
* **패턴 2**: boolean 배열 → 자동 덮어씌워지지 않음 → 명시적 되돌리기 필수
* **패턴 3**: StringBuilder → 자동 덮어씌워지지 않음 → 명시적 되돌리기 필수
* **패턴 4**: 인덱스 + StringBuilder → 자동 덮어씌워지지 않음 → 명시적 되돌리기 필수

---

## 8-1️⃣ 백트래킹 함수 구성 패턴

### 일반적인 함수 구성 패턴들

백트래킹 문제마다 함수 구성이 조금씩 다르지만, **공통된 패턴**이 있습니다.

#### 패턴 1: 깊이(depth) 기반

**적용 상황**: 순열/조합 생성, 고정된 개수의 선택

```java
static void backtrack(int depth) {
if (depth == 목표_깊이) {
// 결과 처리
return;
}

for (int i = 시작값; i <= 끝값; i++) {
result[depth] = i; // 선택
backtrack(depth + 1); // 재귀 호출
// 상태 되돌리기 불필요 (다음 반복에서 덮어씌워짐)
}
}
```

**사고 과정:**
- 몇 개를 선택해야 하는지가 명확할 때
- 각 단계가 독립적일 때
- 전역 변수로 상태 관리

#### 패턴 2: 위치 기반

**적용 상황**: 2차원 보드 탐색, 그래프 탐색

```java
static void backtrack(int r, int c, int count) {
if (제약_조건_위반) {
return; // 가지치기
}

visited[상태] = true; // 상태 변경

for (int i = 0; i < 방향_개수; i++) {
int nr = r + dr[i];
int nc = c + dc[i];
if (isValid(nr, nc)) {
backtrack(nr, nc, count + 1);
}
}

visited[상태] = false; // ✅ 상태 되돌리기 필수!
}
```

**사고 과정:**
- 위치 정보가 핵심일 때
- 공유 상태를 사용할 때
- 상태 되돌리기가 필수

#### 패턴 3: 인덱스 + 누적 상태

**적용 상황**: 문자열 조합, 독립적인 호출 필요

```java
static void generateCombinations(
String source, int targetSize, int start,
StringBuilder current, Map<String, Integer> result
) {
if (current.length() == targetSize) {
result.put(current.toString(), ...);
return;
}

if (가지치기_조건) {
return;
}

for (int i = start; i < source.length(); i++) {
current.append(source.charAt(i)); // 선택
generateCombinations(source, targetSize, i + 1, current, result);
current.setLength(current.length() - 1); // ✅ 상태 되돌리기
}
}
```

**사고 과정:**
- 각 호출이 독립적이어야 할 때
- 누적 상태를 관리할 때
- 인자로 모든 정보 전달

### 패턴 선택 가이드

**어떤 패턴을 선택할까?**

1. **깊이 기반**: 선택할 개수가 명확하고, 각 단계가 독립적
2. **위치 기반**: 위치 정보가 핵심이고, 공유 상태 사용
3. **인덱스 기반**: 독립적인 호출이 필요하고, 누적 상태 관리

### 함수 설계 원칙

**사고 과정:**

1. **상태 정보 결정**
- 현재 진행 상황은 무엇인가? (depth, 위치, 인덱스)
- 현재 상태는 무엇인가? (선택한 값들, 방문 정보)
- 목표 정보는 무엇인가? (목표 크기, 목표 위치)
- 결과는 어디에 저장할까? (전역 변수, 인자)

2. **전역 vs 인자**
- 여러 호출에서 공유해야 하는가? → 전역
- 각 호출이 독립적이어야 하는가? → 인자

3. **상태 되돌리기 판단**
- 상태가 공유되는가? → 필수
- 상태가 독립적인가? → 불필요

---

Expand Down
Loading