본문 바로가기
코딩 테스트/다이나믹 프로그래밍

다이나믹 프로그래밍 개념

by 서영선 2023. 7. 23.

 

한번 계산한 문제는 다시 계산하지 않도록 하는 알고리즘으로, 피보나치 수열이 다이나믹 프로그래밍으로 해결할 수 있는 대표적인 예시이다.

 

피보나치 수열의 점화식을 재귀 함수를 이용해 만들 수는 있지만, 단순히 매번 계산하도록 하면 문제를 효율적으로 해결할 수 없다. 이러한 문제는 다이나믹 프로그래밍을 사용하면 효율적으로 해결할 수 있다.

 

 

 

다이나믹 프로그래밍은 조건은 이렇다.

1. 큰 문제를 작은 문제로 나눌 수 있다.
2. 작은 문제에서 구한 정답은 그것을 포함하는 큰 문제에서도 동일하다.

 

 

 

다이나믹 프로그래밍 적용 안한 케이스

def fibo(x):
	if x==1 or x==2:
    	return 1
    return fibo(x-1) + fibo(x-2)
    
print(fibo(4))

 

 

다이나믹 프로그래밍 적용한 케이스

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]
 
pring(fibo(99))

 

 

한 번 구한 결과는 다시 구해지지 않는다. 따라서 실제로 호출되는 함수에 대해서만 확인해보면 시간 복잡도가 O(N)이라는 것을 이해할 수 있다.

 

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

 

 

 

 

 

실전 문제

 

1.  1로 만들기

 

<문제>

 

x가 5로 나누어 떨어지면, 5로 나눈다.

x가 3으로 나누어 떨어지면, 3으로 나눈다.

x가 2로 나누어 떨어지면, 2로 나눈다.

x에서 1을 뺀다.

 

정수 x가 주어졌을 때, 연산 4개를 적절히 사용해서 1을 만들려고 한다. 연산을 사용하는 횟수의 최솟값을 출력하시오

 

<입력>

26

<출력>

 

 

<풀이>

이 문제는 잘 알려진 다이나믹 프로그래밍 문제이다. 피보나치 수열 문제를 도식화했던 것처럼 문제를 풀기 전에 함수가 호출되는 과정을 그림으로 그려보면 이해하는 데 도움이 된다.

 

 

x = int(input())

d = [0] * 30001

for i in range(2, x+1):
	d[i] = d[i-1] + 1
    
    if i % 2 == 0:
    	d[i] = min(d[i], d[i//2] + 1)
    if i % 3 ==0:
    	d[i] = min(d[i], d[i//3] + 1)
    if i % 5 ==0:
    	d[i] = min(d[i], d[i//5] + 1)
        
 print(d[x])

 

 

 

 

 

 

2. 개미 전사

 

<문제>

개미 전사는 부족한 식량을 충당하고자 메뚜기 마을의 식량 창고를 몰래 공격하려고 한다. 메뚜기 마을에는 여러 개의 식량 창고가 있는데 식량창고는 일직선으로 이어져 있다. 각 식량 창고에는 정해진 수의 식량의 저장하고 있으며, 개미 전사는 식량 창고를 선택적으로 약탈하여 식량을 빼앗을 예정이다. 이때, 메뚜기 정찰병들은 일직선 상에 존재하는 식량 창고 중에서 서로 인접한 식량 창고가 공격받으면 바로 알아챌 수 있다. 따라서 개미 전사가 정찰병에게 들키지 않고, 식량 창고를 약탈하기 위해서는 최소한 한 칸 이상 떨어진 식량 창고를 약탈해야 한다. 개미전사가 얻을 수 있는 식량의 최댓값을 구하시오.

 

<입력>

4                        # 식량 창고의 개수

1 3 1 5               # 각 식량창고에 저장된 식량의 개수

 

<출력>

8

 

 

<풀이>

왼쪽 부터 차례대로 식량 창고를 턴다고 가정하면 어렵지 않게 점화식을 세울 수 있다. 왼쪽부터 차례대로 식량 창고를 털지 안털지를 결정하는 경우와 특정한 i 번째 식량 창고에 대해서 털지 안 털지의 여부를 결정할 때, 2 가지만 확인하면 된다.

 

1. (i - 1) 번째 식량 창고를 털기로 결정한 경우 현재의 식량 창고를 털 수 없다.

2. (i - 2) 번째 식량 창고를 털기로 결정한 경우 현재의 식량 창고를 털 수 있다.

 

 

<코드>

n = int(input())
array = list(map(int, input().split()))

d = [0] * 100

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])

 

 

 

 

 

3. 바닥 공사

가로가 N, 세로가 2인 직사각형 형태의 얇은 바닥이 있다. 태일이는 이 얇은 바닥을 1X2의 덮개, 2X1의 덮개, 2X2의 덮개를 이용해 채우고자 한다.

이때, 바닥을 채우는 모든 경우의 수를 구하여라

 

 

<입력>

3

 

<출력> 바닥을 채우는 방법의 수를 796796으로 나눈 나머지를 출력한다.

5

 

<풀이>

이 문제 또한 마찬가지로 다이나믹 프로그래밍의 기초 예제에서 빠질 수 없는 타일링 문제 유형이다. 다이나믹 프로그래밍 문제에서는 종종 결과를 어떤 수로 나는 결과를 출력하라는 내용이 들어가 있는 경우가 많다.

 

왼쪽부터 차례대로 바닥을 덮개로 채운다고 생각하면 어렵지 않게 점화식을 세울 수 있다.

또한, 이 문제 역시 i번째 위치에 대한 최적의 해를 구할 때, 왼쪽부터 (i - 3)번째 이하의 위치에 대한 최적의 해에 대해서는 고려할 필요가 없다. 왜냐하면 사용할 수 있는 덮개의 형태가 최대 2X2 크기의 직사각형 형태이기 때문이다. 

 

 

 

<코드>

n = int(input())
d = [0] * 1001

d[1] = 1
d[2] = 3
for i in range(3, n+1):
    d[i] = (d[i-1] + 2 * d[i-2]) % 796796

print(d[n])

 

 

 

 

 

 

4. 효율적인 화폐 구성

 

<문제>

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

첫째 줄에 N, M이 주어지고, 이후에 각 화폐의 가치가 주어진다. M을 만들기 위한 최소한의 화폐 개수를 구하여라.

불가능할 때는 -1을 출력하라

 

<입력>

2 15

2

3

 

<출력>

5

 

 

n, m = map(int, input().split())

array = []
for i in range(n):
    array.append(int(input()))

d = [10001] * (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])

 

 

 

 

 

댓글