티스토리 뷰
🔷 부분수열의 합: DFS의 문제점과 DP로의 전환
✅ 1. DFS 방식의 문제점
배열이 주어졌을 때, 숫자를 하나씩 선택하거나 선택하지 않아 만들 수 있는 모든 합을 찾는 문제를 생각합니다.
예시로 다음 배열을 생각합니다.
A = [4, 7, 2, 5]
각 숫자는 독립적으로 선택(1)하거나 선택하지 않음(0)을 결정합니다. 이 과정에서 모든 가능한 경우의 수를 살펴보는 것이 DFS입니다.
모든 경우를 비트열(0과 1의 조합)로 표현하면 다음과 같습니다.
| 비트열 | 선택된 숫자 | 합 |
|---|---|---|
| 0000 | 없음 | 0 |
| 0001 | 5 | 5 |
| 0010 | 2 | 2 |
| 0011 | 2+5 | 7 |
| 0100 | 7 | 7 |
| 0101 | 7+5 | 12 |
| 0110 | 7+2 | 9 |
| 0111 | 7+2+5 | 14 |
| 1000 | 4 | 4 |
| 1001 | 4+5 | 9 |
| 1010 | 4+2 | 6 |
| 1011 | 4+2+5 | 11 |
| 1100 | 4+7 | 11 |
| 1101 | 4+7+5 | 16 |
| 1110 | 4+7+2 | 13 |
| 1111 | 4+7+2+5 | 18 |
이 과정에서 문제점은 같은 합을 중복하여 계산한다는 것입니다.
예를 들어,
- 합이
7인 경우:(0011) 2+5,(0100) 7 - 합이
9인 경우:(0110) 7+2,(1001) 4+5 - 합이
11인 경우:(1011) 4+2+5,(1100) 4+7
이렇게 같은 합을 여러 번 중복으로 탐색하여 시간이 불필요하게 낭비됩니다.
✅ 2. DP로 해결 아이디어
DFS의 중복된 계산 문제를 해결하기 위한 아이디어가 DP(Dynamic Programming, 동적계획법)입니다.
DP의 핵심 아이디어는 이미 계산한 결과를 메모해두고, 다시 사용하여 중복 계산을 하지 않는 것입니다.
📌 무엇을 기록할지
DP 배열에서 기록할 내용은 다음과 같습니다.
- 지금까지 만든 합이 가능한지 불가능한지 (true or false)
예시 배열 [4,7,2,5]를 사용하면, 만들 수 있는 최대 합은 \(4+7+2+5=18\)입니다. 즉, DP 배열의 크기는 \(18+1 = 19\)가 필요합니다.
다음과 같이 정의합니다.
- \(DP[x]\) : 합 \(x\)를 만들 수 있는가? (true 또는 false)
처음 아무 숫자도 선택하지 않았으므로 초기 DP 상태는 다음과 같습니다.
DP[0] = true, 나머지 DP[1~18] = false
📌 점화식 설정 방법
배열의 각 숫자를 하나씩 보면서, 지금까지 가능한 합에서 새로 숫자를 추가할 때마다 다음 점화식을 사용합니다.
DP[현재 합 + 새 숫자] = true
여기서 주의할 점은 반드시 DP 배열을 뒤에서부터 앞으로 업데이트해야 합니다. 이유는 앞에서부터 업데이트하면 같은 단계에서 새롭게 추가된 값이 다시 중복 사용될 가능성이 있기 때문입니다.
구체적으로, 숫자 7을 처리할 때를 예로 들면:
- 현재 가능한 합
{0,4}에서 - 7을 더해
{7,11}이 추가로 가능해집니다. - 이때 반드시 18에서 0 방향으로 업데이트해야 중복이 발생하지 않습니다.
📌 DP 배열을 예시로 업데이트 하는 과정
예시 배열 [4,7,2,5]를 DP로 업데이트 하는 과정을 애니메이션처럼 단계적으로 살펴봅니다.
📌 초기상태
DP[0]=true // 합 0은 항상 가능
📌 첫 번째 숫자 (4)를 추가로 고려
- 기존 DP:
{0} - 추가 가능한 합:
{0+4=4} - 업데이트 결과:
{0,4}
DP[0]=true, DP[4]=true
📌 두 번째 숫자 (7)를 추가로 고려
- 기존 DP:
{0,4} - 추가 가능한 합:
{0+7=7, 4+7=11} - 업데이트 결과:
{0,4,7,11}
DP[0]=true, DP[4]=true, DP[7]=true, DP[11]=true
📌 세 번째 숫자 (2)를 추가로 고려
- 기존 DP:
{0,4,7,11} - 추가 가능한 합:
{0+2=2, 4+2=6, 7+2=9, 11+2=13} - 업데이트 결과:
{0,2,4,6,7,9,11,13}
DP[0]=true, DP[2]=true, DP[4]=true, DP[6]=true, DP[7]=true, DP[9]=true, DP[11]=true, DP[13]=true
📌 네 번째 숫자 (5)를 추가로 고려
- 기존 DP:
{0,2,4,6,7,9,11,13} - 추가 가능한 합:
{0+5=5, 2+5=7(중복), 4+5=9(중복), 6+5=11(중복), 7+5=12, 9+5=14, 11+5=16, 13+5=18} - 중복을 제외한 최종 결과:
{0,2,4,5,6,7,9,11,12,13,14,16,18}
이 과정에서 DP 배열은 숫자를 추가할 때마다 새로운 가능한 합을 기록하면서 중복되는 합은 추가하지 않습니다.
✅ 3. DP의 코드 구현
#include
using namespace std;
int main() {
int A[4] = {4,7,2,5};
bool DP[19] = {false}; // 가능한 합 최대 18이므로 0~18까지
DP[0] = true; // 아무것도 안 고른 상태는 합 0 가능
for(int i=0; i<4; i++){
int num = A[i];
// DP 배열을 뒤에서 앞으로 업데이트해서 중복 방지
for(int sum=18; sum>=0; sum--){
if(DP[sum] == true && sum+num<=18){
DP[sum+num] = true;
}
}
}
// 결과 출력
cout << "가능한 합들: ";
for(int sum=0; sum<=18; sum++){
if(DP[sum] == true) cout << sum << " ";
}
return 0;
}
📌 상세한 코드 설명
- 배열 A: 숫자 저장
- DP 배열: 합이 가능한지 여부 저장 (
true이면 가능) - 처음 상태로
DP[0]=true설정 (숫자를 안고른 상태) - 각 숫자를 추가하면서 기존 가능한 합에 추가하여 DP 업데이트
- 뒤에서 앞으로 내려오면서 업데이트하여 같은 숫자를 중복으로 계산하는 문제를 해결합니다.
- 최종 DP 배열에 기록된 값이 가능한 모든 합입니다.
📌 위 코드의 실제 출력결과
가능한 합들: 0 2 4 5 6 7 9 11 12 13 14 16 18
✅ 결론
- DFS는 같은 합을 여러 번 중복 계산해 시간이 오래 걸립니다.
- DP는 이미 계산한 결과를 저장하고 재사용하여 중복을 없애 빠릅니다.
- DP 배열을 사용해 중복 계산을 막을 수 있으며, 간단한 배열만으로 구현할 수 있습니다.
- STL 없이도 간단한 고정 배열로 효율적으로 구현 가능합니다.
'알고리즘 > 동적계획법' 카테고리의 다른 글
| 동적 계획법(Dynamic Programming)이란? - Bottom-Up 방식: 테뷸레이션 (0) | 2025.06.29 |
|---|---|
| 동적계획법(Dynamic Programming)이란? - 메모이제이션 (0) | 2025.06.29 |
| 동적계획법(Dynamic Programming)이란? 문제편 (0) | 2025.06.29 |
- Total
- Today
- Yesterday
- 상속
- python 알고리즘
- c언어
- 코딩
- 동적계획법
- 브루트포스
- 알고리즘 문제풀이
- 그리디알고리즘
- 코딩테스트
- 알고리즘
- 코딩 테스트
- 동적 계획법
- dfs
- 프로그래밍
- Python
- 문자열처리
- 문제 풀이
- 알고리즘문제풀이
- 백준
- C++
- 파이썬코딩
- 파이썬
- 그리디
- DP
- 문제풀이
- 알고리즘기초
- 객체지향
- HTML
- 프로그래머스
- 그래프 탐색
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
