[유형별 기출문제] 다이나믹 프로그래밍
본 포스팅은 나동빈 저자의 ‘이것이 취업을 위한 코딩테스트’를 공부하며 정리한 노트입니다.
다이나믹 프로그래밍
어떤 문제는 메모리 공간을 약간 더 사용하면 연산 속도를 비약적으로 증가시킬 수 있는 방법이 있다. 대표적인 방법이 다이나믹 프로그래밍이다. 다이나믹 프로그래밍은 다음 조건을 만족할 때 사용할 수 있다.
- 큰 문제를 작은 문제로 나눌 수 있을 때
- 작은 문제에서 구한 정답은 그것을 포함하는 큰 문제에서도 동일할 때
정리하자면 다이나믹 프로그래밍이란 큰 문제를 작게 나누고, 한 번 해결된 부분 문제의 정답을 메모리에 기록하여, 문제를 효율적으로 해결하는 알고리즘 기법이다.
다이나믹 프로그래밍 문제를 푸는 방법
완전 탐색 알고리즘으로 접근했을 때 시간이 매우 오래 걸리면 다이나믹 프로그래밍을 적용할 수 있는지 해결하고자 하는 부분 문제들의 중복 여부를 확인해본다. 문제를 풀기 전에 문제를 도식화하여 동일하게 여러 번 호출되는 과정이 있는지 살펴본다. 문제에서 요구하는 내용을 점화식(인접한 항들 사이의 관계식)으로 표현해본다. 이 점화식을 토대로 소스코드를 작성한다. 다이나믹 프로그래밍을 이용한 소스코드를 작성하는 방법 2가지가 있다.
1) 탑다운
재귀함수를 이용하여 큰 문제를 해결하기 위해 작은 문제를 호출하는 방식
2) 보텀업
반복문을 이용하여 작은 문제를 먼저 해결하고, 해결된 작은 문제를 모아 큰 문제를 해결하는 방식
Q31. 금광
def reshape(arr):
_array = []
row = []
for i in range(n * m):
if i % m == 0 and i > 0:
_array.append(row)
row = []
row.append(arr[i])
_array.append(row)
return _array
t = int(input())
for _ in range(t):
n, m = map(int, input().split())
array = reshape(list(map(int, input().split())))
dp = [[0] * m for _ in range(n)]
for i in range(n):
dp[i][0] = array[i][0]
for j in range(m - 1):
for i in range(n):
if i - 1 >= 0 and i + 1 < n:
dp[i][j + 1] = max(dp[i - 1][j], dp[i][j], dp[i + 1][j]) + array[i][j + 1]
elif i - 1 >= 0 and i + 1 >= n:
dp[i][j + 1] = max(dp[i - 1][j], dp[i][j]) + array[i][j + 1]
else:
dp[i][j + 1] = max(dp[i][j], dp[i + 1][j]) + array[i][j + 1]
answer = 0
for i in range(n):
answer = max(answer, dp[i][m - 1])
print(answer)
입력: 테스트 케이스 T, 금광의 크기 N과 M
출력: 테스트 케이스마다 채굴자가 얻을 수 있는 금의 최대 크기
문제 해결 방법: 2차원 배열에 부분 문제의 정답을 저장한다. 즉, 채굴자가 오른쪽 위, 오른쪽, 오른쪽 아래 3가지지 중 하나의 위치로 이동할 때 얻을 수 있는 금의 최대 크기를 저장한다. 단, 이동하면서 주어진 범위의 인덱스를 벗어나지 않도록 구현한다.
점화식: dp[i][j + 1] = max(dp[i - 1][j], dp[i][j], dp[i + 1][j]) + array[i][j + 1]
Q32. 정수 삼각형
n = int(input())
array = []
for _ in range(n):
array.append(list(map(int, input().split())))
dp = [[0] * n for _ in range(n)]
dp[0][0] = array[0][0]
for i in range(1, n):
for j in range(n):
if i < j:
continue
if j - 1 >= 0 and j < i:
dp[i][j] = max(dp[i - 1][j - 1], dp[i - 1][j]) + array[i][j]
elif j - 1 >= 0 and j == i:
dp[i][j] = dp[i - 1][j - 1] + array[i][j]
else:
dp[i][j] = dp[i - 1][j] + array[i][j]
answer = max(dp[n - 1])
print(answer)
입력: 삼각형의 크기 n, 정수 삼각형
출력: 합이 최대가 되는 경로에 있는 수의 합
문제 해결 방법: 2차원 배열에 부분 문제의 정답을 저장한다. 즉, 모든 위치를 기준으로 ‘왼쪽 위’ 혹은 ‘바로 위’ 위치까지의 최적의 합 중에서 더 큰 합을 가지는 경우를 선택하여 내려온다. 단, 이동하면서 주어진 범위의 인덱스를 벗어나지 않도록 구현한다.
점화식: dp[i][j] = max(dp[i - 1][j - 1], dp[i - 1][j]) + array[i][j]
Q33. 퇴사
n = int(input())
t_arr, p_arr = [0], [0]
d = [0] * (n + 1)
for _ in range(n):
t, p = map(int, input().split())
t_arr.append(t)
p_arr.append(p)
for i in range(n, 0, -1):
consulting_end_date = i + t_arr[i]
if consulting_end_date > n + 1:
d[i] = 0
continue
elif consulting_end_date == n + 1:
d[i] = p_arr[i]
else:
d[i] = p_arr[i] + max(d[i + t_arr[i]:])
print(max(d))
입력: 퇴사까지 남은 날 N, N일 동안의 상담 일정
출력: 상담을 통해 얻을 수 있는 최대 이익
문제 해결 방법: 1차원 배열에 부분 문제의 정답을 저장한다. 뒤쪽 날짜부터 거꾸로 확인하는 방식으로 접근한다. 즉, 뒤쪽부터 매 상담에 대하여 ‘현재 상담 일자의 이윤 + 현재 상담을 마친 일자부터의 최대 이윤’을 계산하면 된다.
점화식: d[i] = p_arr[i] + max(d[i + t_arr[i]:])
Q34. 병사 배치하기
n = int(input())
array = list(map(int, input().split()))
dp = [1] * n
for i in range(1, n):
for j in range(i):
if array[i] < array[j] and dp[i] < dp[j] + 1:
dp[i] = dp[j] + 1
print(n - max(dp))
입력: 병사의 수 N, 병사의 전투력
출력: 조건에 맞게 병사가 남아 있도록 하기 위하여 열외시켜야 하는 병사의 수
문제 유형: 가장 긴 증가하는 부분 수열
점화식: 모든 0 ≤ j < i 에 대하여, dp[i] = max(dp[i], dp[j] + 1) if array[i] < array[j]
Q35. 못생긴 수
import heapq
# n번째 못생긴 수를 구하라
n = int(input())
h = [1]
cnt = 0
prev = 0
while True:
ugly = heapq.heappop(h)
if prev == ugly:
continue
cnt += 1
heapq.heappush(h, ugly * 2)
heapq.heappush(h, ugly * 3)
heapq.heappush(h, ugly * 5)
prev = ugly
if cnt == n:
break
print(ugly)
입력: n
출력: n번째 못생긴 수
문제 해결 방법: 못생긴 수 1을 2, 3, 5를 점진적으로 곱하여 못생긴 수를 만든다. 단, 오름차순으로 못생긴 수를 정렬하며 찾기 위하여 가장 작은 못생신 수를 반환하는 최소 힙 자료구조를 사용한다.
Q36. 편집 거리
str1 = input()
str2 = input()
n, m = len(str1), len(str2)
d = [[0] * m for _ in range(n)]
for i in range(n):
d[i][0] = i
for j in range(m):
d[0][j] = j
for i in range(1, n):
for j in range(1, m):
if str1[i] == str2[j]:
d[i][j] = d[i - 1][j - 1]
else:
d[i][j] = min(d[i - 1][j], d[i - 1][j - 1], d[i][j - 1]) + 1
print(d[-1][-1])
입력: 두 문자열 A, B
출력: 문자열 A를 편집하여 문자열 B로 만들기 위해 사용한 최소 연산의 수 (최소 편집 거리)
문제 유형: 최소 편집 거리 알고리즘
점화식:
1) 두 문자가 같은 경우
d[i][j] = d[i - 1][j - 1]
2) 두 문자가 다른 경우
d[i][j] = min(d[i - 1][j], d[i - 1][j - 1], d[i][j - 1]) + 1
댓글남기기