알고리즘은 공부하다가 분할 정복알고리즘 이후 훨씬 효율적인 알고리즘인 다이나믹 프로그래밍 알고리즘이 방법이 있다고 해서 궁금해서 공부해보았습니다.
※ 본 교안은 "이것이 취업을 위한 코딩 테스트다 with 파이썬"을 참고하여 제작되었음을 알립니다
피보나치 수열 다음과 같은 형태의 수열이며, 다이나믹 프로그래밍으로 효과적으로 계산할 수 있습니다.
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
점화식이란 인접한 항들 사이의 관계식을 의미합니다.
피보나치 수열을 점화식으로 표현하면 다음과 같습니다.
# 피보나치 함수(Fibonacci Function)을 재귀함수로 구현
def fibo(x):
if x == 1 or x == 2:
return 1
return fibo(x - 1) + fibo(x - 2)
print(fibo(4))
실행 결과
3
# 한 번 계산된 결과를 메모이제이션(Memoization)하기 위한 리스트 초기화
d = [0] * 100 # 99번째 피보나치 수를 구하기 위해 0~99인덱스를 가지는 리스트로 초기화
# 피보나치 함수(Fibonacci Function)를 재귀함수로 구현 (탑 다운-다이나믹 프로그래밍)
def fibo(x):
# 바닥 조건 혹은 종료 조건(1 혹은 2일 때 1을 반환)
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))
실행 결과
218922995834555169026
# 앞서 계산된 결과를 저장하기 위한 DP 테이블 초기화
d = [0] * 100
# 첫 번째 피보나치 수와 두 번째 피보나치 수는 1
d[1] = 1
d[2] = 1
n = 99
# 피보나치 함수(Fibonacci Function) 반복문으로 구현(바텀 업-다이나믹 프로그래밍)
for i in range(3, n + 1):
d[i] = d[i - 1] + d[i - 2]
print(d[n])
실행 결과
218922995834555169026
# 한 번 계산된 결과를 메모이제이션(Memoization)하기 위한 리스트 초기화
d = [0] * 100
# 피보나치 함수(Fibonacci Function)를 재귀함수로 구현 (탑 다운-다이나믹 프로그래밍)
def fibo(x):
print('f(' + str(x) + ')', end=' ')
# 종료 조건(1 혹은 2일 때 1을 반환)
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]
fibo(6)
실행 결과
f(6) f(5) f(4) f(3) f(2) f(1) f(2) f(3) f(4)
수행시간: 1초
입력 조건:
출력 조건:
입력 예시:
4
1 3 1 5
출력 예시:
8
예시를 확인해 봅시다. N = 4일 때, 다음과 같은 경우들이 존재할 수 있습니다.
7번째 경우에서 8만큼의 식량을 얻을 수 있으므로 최적의 해는 8입니다.
𝒂ᵢ = 𝑖번째 식량창고까지의 최적의 해 (얻을 수 있는 식량의 최댓값)
⭐왼쪽부터 차례대로 식량창고를 턴다고 했을 때, 특정한 𝑖번째 식량창고에 대해서 털지 안 털지의 여부를 결정하면, 아래 2가지 경우 중에서 더 많은 식량을 털 수 있는 경우를 선택하면 됩니다.(𝑖 - 3은 고려x)
(=> 현재 위치까지 얻을 수 있는 식량의 최댓값)
# 정수 N을 입력 받기
n = int(input())
# 모든 식량 정보 입력 받기
array = list(map(int, input().split()))
# 앞서 계산된 결과를 저장하기 위한 DP 테이블 초기화
d = [0] * 100 # N <= 100 이므로 d.length() = 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])
정수 X가 주어졌을 때, 정수 X에 사용할 수 있는 연산은 다음과 같이 4가지 입니다.
정수 X가 주어졌을 때, 연산 4개를 적절히 사용해서 값을 1로 만들고자 한다. 연산을 사용하는 횟수의
최솟값을 출력하세요. 예를 들어 정수가 26이면 다음과 같이 계산해서 3번의 연산(4 -> 1 -> 1)이 최솟값입니다.
수행 시간: 1초
입력 조건:
출력 조건:
입력 예시:
26
출력 예시:
3
# 정수 X를 입력 받기
x = int(input())
# 앞서 계산된 결과를 저장하기 위한 DP 테이블 초기화
d = [0] * 30001 # X<= 30000 이므로
'''1일 경우 이미 자신의 값이 1이므로 연산 횟수 0이라고 할 수 있습니다.'''
# 다이나믹 프로그래밍(Dynamic Programming) 진행(바텀 업, 반복문 사용)
for i in range(2, x + 1):
'''각 경우마다 연산을 한 번씩 수행한 것이므로 +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])
수행 시간: 1초
입력 조건:
출력 조건:
입력 예시1:
2 15
2
3
출력 예시1:
5
입력 예시2:
3 4
3
5
7
출력 예시2:
-1
𝑁 = 3, 𝑀 = 7이고, 각 화폐의 단위가 2, 3, 5인 경우 확인해 봅시다.
𝑁 = 3, 𝑀 = 7이고, 각 화폐의 단위가 2, 3, 5인 경우 확인해 봅시다.
𝒂ᵢ₋ₖ를 만드는 방법이 존재하는 경우, 𝒂ᵢ = min(𝒂ᵢ, 𝒂ᵢ₋ₖ + 1)
𝑁 = 3, 𝑀 = 7이고, 각 화폐의 단위가 2, 3, 5인 경우 확인해 봅시다.
𝒂ᵢ₋ₖ를 만드는 방법이 존재하는 경우, 𝒂ᵢ = min(𝒂ᵢ, 𝒂ᵢ₋ₖ + 1)
𝑁 = 3, 𝑀 = 7이고, 각 화폐의 단위가 2, 3, 5인 경우 확인해 봅시다.
𝒂ᵢ₋ₖ를 만드는 방법이 존재하는 경우, 𝒂ᵢ = min(𝒂ᵢ, 𝒂ᵢ₋ₖ + 1)
# 정수 N, M을 입력 받기
n, m = map(int, input().split())
# N개의 화폐 단위 정보를 입력 받기
array = []
for i in range(n):
array.append(int(input()))
# 한 번 계산된 결과를 저장하기 위한 DP 테이블 초기화
d = [10001] * (m + 1) # 0원 부터 m원 까지 각 금액에 대한 최소한의 화폐개수를 구해서 저장할 것이므로
'''java에선 Arrays.fill(d, 10001)로 값 초기화 가능'''
# 다이나믹 프로그래밍(Dynamic Programming) 진행(바텀 업, 반복문 사용)
d[0] = 0
for k in range(n): # k는 각 화폐 단위를 의미
for i in range(array[k], m + 1): # i는 각 금액을 의미
if d[i - array[k]] != 10001: # (i - k)원을 만드는 방법이 존재하는 경우
'''현재금액 - 현재 확인하고있는 화폐 단위의 금액'''
d[i] = min(d[i], d[i - array[k]] + 1)
'''java에선 Math.min()으로 가능'''
# 계산된 결과 출력
if d[m] == 10001: # 최종적으로 M원을 만드는 방법이 없는 경우
print(-1)
else:
print(d[m])
수행 시간: 1초
입력 조건:
출력 조건:
입력 예시:
2
3 4
1 3 3 2 2 1 4 1 0 6 4 7
4 4
1 3 1 5 2 2 4 1 5 0 2 3 0 6 1 2
출력 예시:
19
16
# 테스트 케이스(Test Case) 입력
for tc in range(int(input())):
# 금광 정보 입력
n, m = map(int, input().split())
array = list(map(int, input().split()))
# 다이나믹 프로그래밍을 위한 2차원 DP 테이블 초기화
dp = []
index = 0
for i in range(n):
dp.append(array[index:index + m])
index += m
# 다이나믹 프로그래밍(Dynamic Programming) 진행(바텀 업, 반복문 사용)
for j in range(1, m):
for i in range(n):
# 왼쪽 위에서 오는 경우
if i == 0: # 테이블 범위 벗어나는지 check
left_up = 0
else:
left_up = dp[i - 1][j - 1]
# 왼쪽 아래에서 오는 경우
if i == n - 1: # 테이블 범위 벗어나는지 check
left_down = 0
else:
left_down = dp[i + 1][j - 1]
# 왼쪽에서 오는 경우
left = dp[i][j - 1]
dp[i][j] = dp[i][j] + max(left_up, left_down, left)
result = 0
for i in range(n):
result = max(result, dp[i][m - 1]) # 가장 오른쪽 열에 있는 값 중 max가 정답
print(result)
수행 시간: 1초
입력 조건:
출력 조건:
입력 예시:
7
15 11 4 8 5 2 4
출력 예시:
2
n = int(input())
array = list(map(int, input().split()))
# 순서를 뒤집어 '최장 증가 부분 수열(LIS)' 문제로 변환
array.reverse()
'''java에선 Collections.reverse(array)'''
# 다이나믹 프로그래밍을 위한 1차원 DP 테이블 초기화
dp = [1] * n
# 가장 긴 증가하는 부분 수열(LIS) 알고리즘 수행 -> LIS의 최대 길이를 구함
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)) # max(dp)는 LIS의 최대 길이이므로
algorithm, 개미전사, 다이나믹프로그래밍, 메모이제이션, 병사배치하기, 분할정복, 알고리즘, 피보나치수열