개념
19주차에서
편집 거리의 세 방향이 각각 무슨 연산인지 짚어두면 외울 필요가 없어요.
dp는 한 줄짜리 표였어요. 이번엔 표가 격자로 커져요. 어렵게 볼 것 없어요. 여전히 한 칸씩 채우는데, 각 칸의 답이 바로 위 칸과 왼쪽 칸(때로는 왼쪽 위 대각선)에서 나온다는 것뿐이에요. 이웃 칸들이 먼저 채워져 있으면, 지금 칸은 그 값들을 더하거나 그중 최댓값을 고르기만 하면 돼요. 스도쿠를 풀듯 왼쪽 위부터 한 칸씩 채워 나가면, 오른쪽 아래 마지막 칸에 답이 적혀요.1차원 DP로 표현되지 않는 문제가 있어요. '몇 번째 물건까지 봤는가'만으로는 부족하고 '가방에 남은 용량이 얼마인가'를 함께 알아야 하는 경우예요. 이럴 때 상태에 축을 하나 더해 dp[i][w]로 만들어요.축이 늘어도 절차는 그대로예요. 상태를 정의하고, 전이를 쓰고, 초기값을 채우고, 순서를 정해요. 다만 상태를 정의할 때 두 축이 각각 무엇을 뜻하는지 분명히 적어야 해요.0-1 배낭
물건마다 무게와 가치가 있고 가방 용량이 정해져 있어요. 각 물건은 넣거나 안 넣거나 둘 중 하나예요(그래서 0-1).dp[i][w]를 'i번째 물건까지 고려했고 용량이 w일 때의 최대 가치'로 두면, 전이는 넣는 경우와 안 넣는 경우의 최댓값이에요.| 종류 | 각 물건 사용 | 내부 루프 방향 | 구분 |
|---|---|---|---|
| 0-1 배낭 | 최대 한 번 | 용량을 내림차순 | 같은 물건 재사용 방지 |
| 무한 배낭 | 제한 없음 | 용량을 오름차순 | 재사용 허용 |
두 문자열을 나란히 놓는 DP
LCS(최장 공통 부분 수열)와 편집 거리는 구조가 같아요.dp[i][j]를 '첫 문자열의 앞 i글자와 둘째 문자열의 앞 j글자를 봤을 때의 답'으로 두고, 두 글자가 같은지에 따라 전이가 갈려요.- LCS — 같으면
dp[i-1][j-1] + 1, 다르면max(dp[i-1][j], dp[i][j-1]) - 편집 거리 — 같으면
dp[i-1][j-1], 다르면 삽입·삭제·교체 세 방향의 최솟값 + 1
dp[i][j-1]에서 오면 삽입, dp[i-1][j]에서 오면 삭제, dp[i-1][j-1]에서 오면 교체예요.롤링 배열
전이가 바로 이전 행만 쓴다면 표 전체를 들고 있을 필요가 없어요. 두 행만 번갈아 쓰거나, 방향을 잘 잡으면 한 행으로도 돼요. 메모리가O(n × m)에서 O(m)으로 줄어들어요. 다만 경로를 복원해야 한다면 표 전체가 필요해요.직접 채워 보기: 격자 경로의 수
3×3 격자의 왼쪽 위에서 오른쪽 아래로 가는데, 오른쪽이나 아래로만 갈 수 있어요. 경로가 몇 가지인지 표를 채워 볼게요.dp[i][j]는 '(i, j)칸에 도달하는 경로 수'예요. 그 칸엔 바로 위(한 칸 아래로)나 바로 왼쪽(한 칸 오른쪽으로)에서만 들어오니까 dp[i][j] = dp[i-1][j] + dp[i][j-1]이에요. 첫 행과 첫 열은 한 줄로만 갈 수 있어 전부 1로 시작해요. 나머지 칸은 위와 왼쪽을 더하기만 하면 돼요.채우는 순서를 보면 각 칸을 구할 때 위 칸과 왼쪽 칸이 이미 채워져 있어요. 그래서 위에서 아래로, 왼쪽에서 오른쪽으로 훑는 2중 반복문이면 순서가 저절로 맞아요. 이게 2차원 DP의 뼈대예요 — 축이 둘로 늘었을 뿐, 19주차와 똑같이 상태·전이·초기값·순서를 정하는 거예요.3x3 격자 — 위+왼쪽으로 한 칸씩 채우기첫 행·첫 열은 전부 1 (한 줄로만 도달)j=0 j=1 j=2i=0 1 1 1i=1 1 . .i=2 1 . .dp[1][1] = dp[0][1] + dp[1][0] = 1 + 1 = 2dp[1][2] = dp[0][2] + dp[1][1] = 1 + 2 = 3dp[2][1] = dp[1][1] + dp[2][0] = 2 + 1 = 3dp[2][2] = dp[1][2] + dp[2][1] = 3 + 3 = 6j=0 j=1 j=2i=0 1 1 1i=1 1 2 3i=2 1 3 6 -> 답 dp[2][2] = 6
패턴 코드
0-1 배낭 (1차원으로 압축)용량 루프가 내림차순인 게 0-1을 보장해요.
javascriptCopy codeconst dp = new Array(capacity + 1).fill(0);for (let i = 0; i < n; i++) {for (let w = capacity; w >= weight[i]; w--) {dp[w] = Math.max(dp[w], dp[w - weight[i]] + value[i]);}}return dp[capacity];
최장 공통 부분 수열
javascriptCopy codeconst dp = Array.from({ length: a.length + 1 }, () =>new Array(b.length + 1).fill(0),);for (let i = 1; i <= a.length; i++) {for (let j = 1; j <= b.length; j++) {dp[i][j] = a[i - 1] === b[j - 1]? dp[i - 1][j - 1] + 1: Math.max(dp[i - 1][j], dp[i][j - 1]);}}
편집 거리초기값은 '빈 문자열로 만들려면 전부 지워야 한다'예요.
javascriptCopy codefor (let i = 0; i <= a.length; i++) dp[i][0] = i;for (let j = 0; j <= b.length; j++) dp[0][j] = j;for (let i = 1; i <= a.length; i++) {for (let j = 1; j <= b.length; j++) {dp[i][j] = a[i - 1] === b[j - 1]? dp[i - 1][j - 1]: 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);}}
이번 주 문제
이번 주 진행0 / 4
셀프 체크
다음 주로 넘어가기 전에 소리 내어 답해 보세요. 막히면 그 부분이 아직 덜 익은 개념이에요.- 격자 경로 DP에서 첫 행과 첫 열을 왜 전부 1로 두나요?
- 0-1 배낭을 1차원으로 줄일 때 용량 루프를 오름차순으로 돌면 어떤 문제가 되나요?
- 편집 거리의 세 전이가 각각 어떤 연산에 대응하나요?
- 롤링 배열을 쓸 수 없는 경우는 언제인가요?