다이나믹 프로그래밍

ezi·2023년 9월 10일
0

알고리즘

목록 보기
10/11

🔊본 포스팅은 '(이코테 2021) 이것이 취업을 위한 코딩 테스트다 with 파이썬' 유튜브 강의를 수강하고 정리한 글입니다.

다이나믹 프로그래밍

다이나믹 프로그래밍은 메모리를 적절히 사용하여 수행 시간 효율성을 비약적으로 향상시키는 방법이다.

이미 계산된 결과(작은 문제)는 별도의 메모리 영역에 저장하여 다시 계산하지 않도록 한다.

다이나믹 프로그래밍의 구현은 일반적으로 두 가지 방식(탑다운과 보텀업)으로 구성된다.

다이나믹 프로그래밍은 동적 계획법이라고도 부른다.

다이나믹 프로그래밍의 조건

다이나믹 프로그래밍은 문제가 다음의 조건을 만족할 때 사용할 수 있다.

  • 최적 부분 구조(Optimal Substructure)
    큰 문제를 작은 문제로 나눌 수 있으며 작은 문제의 답을 모아서 큰 문제를 해결할 수 있다.

  • 중복되는 부분 문제(Overlapping Subproblem)
    동일한 작은 문제를 반복적으로 해결해야 한다.

피보나치 수열

피보나치 수열 다음과 같은 형태의 수열이며, 다이나믹 프로그래밍으로 효과적으로 계산할 수 있다.

점화식이란 인접한 항들 사이의 관계식을 의미한다.

다음은, 피보나치 수열을 점화시으로 표현한 것이다.

# 피보나치 함수(Fibonacci Function)을 재귀함수로 구현
def fibo(x):
	if x==1 or x==2:
    	return 1
    return fibo(x-1)+fibo(x-2)
    
print(fibo(4))

메모이제이션(Memoization)

메모이제이션은 다이나믹 프로그래밍을 구현하는 방법 중 하나이다.
한 번 계산한 결과를 메모리 공간에 메모하는 기법이다.

  • 같은 문제를 다시 호출하면 메모했던 결과를 그대로 가져온다.
  • 값을 기록해 놓는다는 점에서 캐싱(Caching)이라고도 한다.

탑다운 vs 보텀업

탑다운(메모이제이션) 방식은 하향식이라고 하며 보텀업 방식은 상향식이라고 한다.

  • 탑다운은 구현 과정에서 재귀 함수를 이용한다.
  • 즉, 큰 문제를 해결하기 위해서 작은 문제들을 재귀적으로 호출하여 작은 문제가 모두 해결되었을 때 실제로 큰 문제에 대한 답까지 얻을 수 있도록 코드를 작성한다.

다이나믹 프로그래밍의 전형적인 형태는 보텀업 방식이다.

  • 결과 저장용 리스트는 DP 테이블이라고 부른다.
    엄밀히 말하면 메모이제이션은 이전에 계산된 결과를 일시적으로 기록해 놓는 넓은 개념을 의미한다.
  • 따라서 메모이제이션은 다이나믹 프로그래밍에 국한된 개념이 아니다.
  • 한 번 계산된 결과를 담아 놓기만 하고 다이나믹 프로그래밍을 위해 활용하지 않을 수도 있다.

[ 예제 1 ] 개미 전사

문제 설명

개미 전사는 부족한 식량을 충당하고자 메뚜기 마을의 식량창고를 몰래 공격하려고 합니다.

메뚜기 마을에는 여러 개의 식량 창고가 있는데 식량 창고는 일직선으로 이어져 있습니다.

각 식량창고에는 정해진 수의 식량을 저장하고 있으며 개미 전사는 식량창고를 선택적으로 약탁하여 식량을 빼앗을 예정입니다.

이때 메뚜기 정찰병들은 일직선상에 존재하는 식량 창고 중에서 서로 인접한 식량창고가 공격받으면 바로 알아챌 수 있습니다.

따라서 개미 전사가 정찰병에게 들키지 않고 식량창고를 약탈하기 위해서는 최소한 한 칸 이상 떨어진 식량창고를 약탈해야 한다.

# 정수 N을 입력 받기
n = int(input())
# 모든 식량 정보 입력 받기
array = list(map(int, input().split()))

# 앞서 계산된 결과를 저장하기 위한 DP 테이블 초기화
d = [0]*100

# 다이나믹 프로그래밍(Dynamic Programming) 진행 (보텀업)
d[0] = array[0]
d[1] = max(array[0], array[1])
for i in range(2,n):
	d[i] = max(d[i-1], d[i-2]+array[i])
    
# 계산된 결과 출력
print(d[n-1])

[ 예제 2 ] 1로 만들기

문제 설명

정수 X가 주어졌을 때, 정수 X에 사용할 수 있는 연산은 다음과 같이 4가지입니다.

1. X가 5로 나누어 떨어지면, 5로 나눕니다.
2. X가 3으로 나누어 떨어지면, 3으로 나눕니다.
3. X가 2로 나누어 떨어지면, 2로 나눕니다.
4. X에서 1을 뺍니다.

정수 X가 주어졌을 때, 연산 4개를 적절히 사용해서 값을 1로 만들고자 합니다.

연산을 사용하는 횟수의 최소값을 출력하세요.

예를 들어 정수가 26이면 다음과 같이 계산해서 3번의 연산이 최소값입니다.
26 → 25 → 5 → 1


문제 해결

# 정수 X를 입력 받기
x = int(input())

# 앞서 계산된 결과를 저장하기 위한 DP 테이블 초기화
d = [0]*30001

# 다이나믹 프로그래밍(Dynamic Programming) 진행 (보텀업)
for i in range(2, x+1):
	# 현재의 수에서 1을 빼는 경우
    d[i] = d[i-1]+1
    # 현재의 수가 2로 나누어 떨어지는 경우
    if i%2==0:
    	d[i]=min(d[i], d[i//2]+1)
    # 현재의 수가 3으로 나누어 떨어지는 경우
    if i%3==0:
    	d[i]=min(d[i], d[i//3]+1)
    # 현재의 수가 5로 나누어 떨어지는 경우
    if i%5==0:
    	d[i]=min(d[i], d[i//5]+1)
        
print(d[x])

[ 예제 3 ] 효율적인 화폐 구성

문제 설명

N가지 종류의 화폐가 있다. 이 화폐들의 개수를 최소한으로 이용해서 그 가치의 합이 M원이 되도록 하려고 한다.

이때 각 종류의 화폐는 몇 개라도 사용할 수 있다.

예를 들어 2원, 3원 단위의 화폐가 있을 때는 15원을 만들기 위해 3원을 5개 사용하는 것이 가장 최소한의 화폐 개수입니다.

M원을 만들기 위한 최소한의 화폐 개수를 출력하는 프로그램을 작성하세요.


문제 해결

n, m = map(int, input().split())
# N개의 화폐 단위 정보를 입력받기
array = []
for i in range(n):
	array.append(int(input()))
    
# 한 번 계산된 결과를 저장하기 위한 DP 테이블 초기화
d = [10001]*(m+1)

# 다이나믹 프로그래밍(Dynamic Programming) 진행(보텀업)
d[0] = 0
for i in range(n):
	for j in range(array[i], m+1):
    	if d[j-array[i]] !- 10001:  # (i-k)원을 만드는 방법이 존재하는 경우
        	d[j] = min(d[j], d[j-array[i]]+1)

# 계산된 결과 출력
if d[m] == 10001:  # 최종적으로 M원을 만드는 방법이 없는 경우
	print(-1)
else:
	print(d[m])
profile
차곡차곡

0개의 댓글