- 메모리를 적절히 사용하여 수행 시간 효율성을 비약적으로 향상 시키는 방법
- 이미 계산된 작은 문제에 대한 결과는 별도 메모리 영역에 저장해 다시 계산하지 않는다.
- 보통 선형 탐색문제를 이렇게 해결하면 비약적으로 시간 효율을 향상 시킬 수 있다.
- 구현은 보통 탑다운, 바텀업으로 구성된다.
자료구조에서 Dynamic Allocation은 프로그램이 실행되는 도중 실행에 필요한 메모리를 할당하는 기법을 의미
다이나믹 프로그래밍에서는 별다른 의미 없이 사용된 단어
- 아래 조건을 만족할 때 사용가능
- Optimal Substructure(최적 부분 구조)
- 큰 문제를 작은문제로 나눌 수 있으며 작은 문제의 답을 모아 큰 문제 해결할 수 있다.
- Overlapping Subproblem(중복되는 부분 문제)
- 동일한 작은 문제를 반복적으로 해결해야 한다.
피보나치 수열
- 점화식이란 인접한 항들 사이의 관계식을 의미한다.
- 피보나치 수열은 점화식으로 표현할 수 있다.
- 다이나믹 프로그래밍으로 효과적으로 계산할 수 있다.
- 수열은 리스트나 배열을 통해 표현하여 연산을 기록한다.
- 이러한 리스트나 배열을 테이블이라고 부르기도한다.
def fibo(x):
if x == 1 or x == 2:
return 1
return fibo(x - 1) + fibo(x - 2)
print(fibo(4))
- 단순 재귀 함수로 피보나치 수열을 해결하면 지수 시간 복잡도를 가지게 된다.
Memoization
- 한번 계산한 결과를 메모리 공간에 메모하는 기법
- Caching이라고도 한다.
- 같은 문제 다시 호출 시 메모했던 결과를 바로 호출
Top-Down vs Bottom-UP
- Top-Down(memoization) 방식은 하향식
- Bottom-UP 방식은 상향식
- 작은 문제를 해결해 나가면서 그 다음의 문제를 해결해 나간다.
- 전형적인 형태이다.
- 결과 저장용 리스트는 DP Table 이라고 부른다.
- 메모이 제이션은 이전 계산된 결과를 일시적으로 기록해 놓은 넓은 개념을 의미한다.
- 다이나믹 프로그래밍에 국한된 개념 X
- 한 번 계산된 결과를 담아 놓기만 하고 다이나믹 프로그래밍 시 활용하지 않을 수 있다. 메미오제이션과 다이나믹 프로그래밍은 다른 개념인 것이다.
d = [0] * 100
def fibo(x):
if x == 1 or x == 2:
return 1
if d[x] != 0:
return d[x]
d[x] = fibo(x-1) + fibo(x-2)
return d[x]
print(fibo(99))
- 이미 계산된 결과를 메모리에 저장하면, 아래에서 처럼 색칠된 노드만 처리할 것을 기대할 수 있다.
d = [0] * 100
d[1] = 1
d[2] = 1
n = 99
for i in range(3, n + 1):
d[i] = d[i - 1] + d[i - 2]
print(d[n])
다이나믹 프로그래밍 VS 분할 정복
- 모두 최적 부분 구조를 가질 때 사용할 수 있다.
- 차이점은 부분 문제의 중복
- 다이나믹 프로그래밍에서는 각 부분 문제들이 서로 영향을 미치며 문제가 중보된다.
- 퀵 정렬의 경우 생각해보면 피벗이 변경해서 자리를 잡으면 기준 원소의 위치는 더이상 바뀌지 않음 -> 한번이 분할이 이뤄지면 다른 곳에 미치는 영향 없음 (부분문제 중복 X)
- 다이나믹 프로그래밍 유형인지 파악하는 것이 중요
- 다른 알고리즘으로 풀이 방법이 떠오르지 않으면 다이나믹 프로그래밍을 고려해본다.
- 일단 탑다운에서 코드를 개선하는 방법 사용 가능
효율적인 화폐 구성
n, m = map(int, input().split())
array = []
for i in range(n):
array.append(int(input())
d = [1001] * (m + 1)
d[0] = 0
for i in range(n):
for j in range(array[i], m+1):
if d[j - array[i]] != 10001:
d[j] = min(d[j], d[j - array[i]] + 1)
if d[m] == 10001:
print(-1)
else:
print(d[m])
- i 번째를 만들기에 필요한 각 화폐 단위마다의 화폐 개수를 바텀업으로 구하면서 최종 m에 필요한 동전 최소 개수를 구한다.
금광
- 금광의 모든 위치에 대해 세가지만 고려하면 된다.
- 왼쪽 위에서 오는 경우
- 왼쪽 아래에서 오는 경우
- 왼쪽에서 오는 경우
- array[i][j] : 금의 양
- dp[i][j] : 해당 요소까지의 최적해 (얻을 수 있는 금의 최댓값)
n, m = map(int, input().split())
array = list(map(int, input().split()))
dp = []
index = 0
for i in range(n):
dp.append(array[index: index + m])
index += m
for j in range(1, m):
for i in range(n):
병사 배치하기
- LIS (Long Increasing Subsequence) 가장 긴 증가하는 부분 수열 알고리즘을 이용
- D[i] = array[i]를 마지막 원소로 가지는 부분 수열의 최대 길이
- 점화식 : 모든 앞쪽에있는 원소 j, 0 <= j < i에 대해 D[i] = max(D[i], D[j] + 1) if array[j] < array[i]
- O(N^2)
...
array.revers() # 순서를 뒤집어 최장 증가 부분 수열 문제로 변환 LIS
dp = [1] * n # 각 원소를 마지막으로 갖는 배열은 하나씩 있으므로 1로 초기화
for i in range(1, n):
for j in range(0, i):
if array[j] < array[i]:
dp[i] = max(dp[i], dp[j] + 1)
print(n - max(dp))