[이코테 Python] 다이나믹 프로그래밍 문제
- -
다이나믹 프로그래밍 문제
다이나믹 프로그래밍
조건
최적 부분 구조 : 큰 문제를 작은 문제로 나눌 수 있으며 작은 문제의 답을 모아서 큰 문제를 해결할 수 있다.
중복되는 부분 문제 : 동일한 작은 문제를 반복적으로 해결해야 한다
전형적으로 보텀업(상향식) 방식을 사용
피보나치 수
단순 재귀형 O(2^n)
def fibonacci(x):
if x == 1 or x == 2:
return 1
else:
return fibonacci(x - 1) + fibonacci(x - 2)
print(fibonacci(30))
DP 탑다운 O(n)
# 한 번 계산된 결과를 메모이제이션하기 위한 리스트 초기화
n = int(input())
d = [0] * (n + 1)
# 피보나치 함수를 재귀함수로 구현 (탑다운 형식)
def fibonacci(x):
if x == 1 or x == 2:
return 1
# 이미 계산한 적 있는 문제라면 그대로 반환
if d[x] != 0:
return d[x]
# 아직 계산하지 않은 문제라면 점화식에 따라 피보나치 결과 반환
d[x] = fibonacci(x - 1) + fibonacci(x - 2)
return d[x]
print(fibonacci(n))
DP 보텀업 O(n)
# 앞서 계산된 결과를 저장하기 위한 DP테이블 초기화
n = int(input())
d = [0] * (n + 1)
d[1] = 1
d[2] = 1
# 피보나치 함수를 반복문으로 구현 (보텀업 형식)
for i in range(3, n + 1):
d[i] = d[i - 1] + d[i - 2]
print(d[n])
금광
[문제]
n x m 크기의 금광이 있습니다. 금광은 1 x 1 크기의 칸으로 나누어져 있으며, 각 칸은 특정한 크기의 금이 들어 있습니다.
채굴자는 첫 번째 열부터 출발하여 금을 캐기 시작합니다. 맨 처음에는 첫 번째 열의 어느 행에서든 출발할 수 있습니다. 이후에 m - 1번에 걸쳐서 매번 오른쪽 위, 오른쪽, 오른쪽 아래 3가지 중 하나의 위치로 이동해야 합니다. 결과적으로 채굴자가 얻을 수 있는 금의 최대 크기를 출력하는 프로그램을 작성하세요.
[입력]
1. 첫째 줄에 테스트 케이스 T가 입력됩니다. (1 <= T <= 1000)
2. 매 테스트 케이스 첫째 줄에 n과 m이 공백으로 구분되어 입력됩니다. (1 <= n, m <= 20)
둘째 줄에 n x m개의 위치에 매장된 금의 개수가 공백으로 구분되어 입력됩니다. (1 <= 각 위치에 매장된 금의 개수 <= 100)
입력 예시
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
개인 풀이1
import numpy as np
t = int(input())
for _ in range(t):
n, m = map(int, input().split())
arr = np.array(list(map(int, input().split())))
arr = arr.reshape(n, m)
golds = arr[:]
for i in range(m):
for j in range(n):
max_val = 0
if j - 1 >= 0 and i - 1 >= 0 and arr[j - 1, i - 1] > max_val:
max_val = arr[j - 1, i - 1]
if j + 1 < n and i - 1 >= 0 and arr[j + 1, i - 1] > max_val:
max_val = arr[j + 1, i - 1] # 2
if i - 1 >= 0 and arr[j, i - 1] > max_val:
max_val = arr[j, i - 1]
arr[j, i] += max_val
print(np.max(arr))
개인 풀이2
t = int(input())
for _ in range(t):
n, m = map(int, input().split())
arr = list(map(int, input().split()))
golds = []
index = 0
for __ in range(n):
golds.append(arr[index:index + 4])
index += 4
for j in range(1, m):
for i in range(n):
if i == 0:
golds[i][j] += max(golds[i][j - 1], golds[i + 1][j - 1])
elif i == n - 1:
golds[i][j] += max(golds[i][j - 1], golds[i - 1][j - 1])
else:
golds[i][j] += max(golds[i][j - 1], golds[i - 1][j - 1], golds[i + 1][j - 1])
max_val = 0
for i in range(n):
if golds[i][m - 1] > max_val:
max_val = golds[i][m - 1]
print(max_val)
정답 풀이
''' 점화식
array[i][j] = i행 j열에 존재하는 금의 양
dp[i][j] = i행 j열까지의 최적의 해
dp[i][j] = array[i][j] + max(dp[i-1][j-1], dp[i][j-1], dp[i+1][j-1])
'''
# 테스트 케이스 수 입력
for t in range(int(input())):
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
# dp 진행
for j in range(1, m):
for i in range(n):
# 왼쪽 위에서 오는 경우
if i == 0:
left_up = 0
else:
left_up = dp[i - 1][j - 1]
# 왼쪽 아래에서 오는 경우
if i == n - 1:
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])
print(result)
병사 배치하기
[문제]
N명의 병사가 무작위로 나열되어 있습니다. 각 병사는 특정한 값의 전투력을 보유하고 있습니다.
병사를 배치할 때는 전투력이 높은 병사가 앞쪽에 오도록 내림차순으로 배치를 하고자 합니다. 다시 말해 앞쪽에 있는 병사의 전투력이 항상 뒤쪽에 있는 병사보다 높아야 합니다.
또한 배치 과정에서는 특정한 위치에 있는 병사를 열외시키는 방법을 이용합니다. 그러면서도 남아 있는 병사의 수가 최대가 되도록 하고 싶습니다.
예를 들어, N = 7일 때 나열된 병사들의 전투력이 다음과 같다고 가정하겠습니다.
이때 3번 병사와 6번 병사를 열외시키면, 다음과 같이 남아 있는 병사의 수가 내림차순의 형태가 되며 5명이 됩니다. 이는 남아 있는 병사의 수가 최대가 되도록 하는 방법입니다.
병사에 대한 정보가 주어졌을 때, 남아 있는 병사의 수가 최대가 되도록 하기 위해서 열외시켜야 하는 병사의 수를 출력하는 프로그램을 작성하세요.
[입력]
1. 첫째 줄에 N이 주어집니다. (1 <= N <= 2,000) 2. 둘째 줄에 병사의 전투력이 공백으로 구분되어 차례대로 주어집니다. 각 병사의 전투력은 10,000,000보다 작거나 같은 자연수입니다.
입력 예시
7
15 11 4 8 5 2 4
[출력]
첫째 줄에 남아 있는 병사의 수가 최대가 되도록 하기 위해서 열외시켜야 하는 병사의 수를 출력합니다.
출력 예시
2
정답 풀이
''' 점화식
d[i] = array[i]를 마지막 원소로 가지는 부분 수열의 최대 길이
모든 0 <= j < i에 대하여, d[i] = max(d[i], d[j] + 1) if array[j] < array[i]
'''
n = int(input())
array = list(map(int, input().split()))
# 순서를 뒤집어 "최장 증가 부분 수열" 문제로 변환
array.reverse()
dp = [1] * n
# 최장 증가 부분 수열(LIS) 알고리즘 수행
for i in range(1, n):
for j in range(i):
if array[j] < array[i]:
d[i] = max(dp[i], dp[j] + 1)
# 열외해야 하는 병사의 최소 수 출력
print(n - max(dp))
개미 전사
[문제]
개미 전사는 부족한 식량을 충당하고자 메뚜기 마을의 식량창고를 몰래 공격하려고 한다. 메뚜기 마을에는 여러 개의 식량창고가 있는데 식량창고는 일직선으로 이어져 있다.
각 식량창고에는 정해진 수의 식량을 저장하고 있으며 개미 전사는 식량창고를 선택적으로 약탈하여 식량을 빼앗을 예정이다. 이때 메뚜기 정찰병들은 일직선상에 존재하는 식량창고 중에서 서로 인접한 식량창고가 공격받으면 바로 알아챌 수 있다.
따라서 개미 전사가 정찰병에게 들키지 않고 식량창고를 약탈하기 위해서는 최소한 한 칸 이상 떨어진 식량창고를 약탈해야 한다. 예를 들어 식량창고 4개가 다음과 같이 존재한다고 가정하자.
1 3 1 5
이때 개미 전사는 두 번째, 네 번째 식량창고를 선택했을 때 최댓값인 총 8개의 식량을 빼앗을 수 있다. 개미 전사는 식량창고가 이렇게 일직선상일 때 최대한 많은 식량을 얻기를 원한다.
개미 전사를 위해 식량창고 N개에 대한 정보가 주어졌을 때 얻을 수 있는 식량의 최댓값을 구하는 프로그램을 작성하시오.
[입력]
첫째 줄에 식량창고의 개수 N이 주어진다.(3 <= N <= 100)
둘째 줄에 공백으로 구분되어 각 식량창고에 저장된 식량의 개수 K가 주어진다.(0 <= K <= 1,000)
입력 예시
4
1 3 1 5
[출력]
첫째 줄에 개미 전사가 얻을 수 있는 식량의 최댓값을 출력하시오
출력 예시
8
개인 풀이
n = int(input())
k = list(map(int, input().split()))
d = [0] * n
d[0] = k[0]
d[1] = max(k[1], k[0])
for i in range(2, n):
d[i] = max(k[i] + d[i - 2], d[i - 1])
print(max(d))
정답 풀이
n = int(input())
foods = list(map(int, input().split()))
d = [0] * 100
# 보텀업 DP 진행
d[0] = foods[0]
d[1] = max(d[0], foods[1])
for i in range(2, n):
d[i] = max(d[i - 2] + foods[i], d[i - 1])
print(d[n - 1])
1로 만들기
[문제]
정수 X가 주어질 때 정수 X에 사용할 수 있는 연산은 다음과 같이 4가지 이다.
X가 5로 나누어 떨어지면 5로 나눈다.
X가 3으로 나누어 떨어지면 3으로 나눈다.
X가 2로 나누어 떨어지면 2로 나눈다.
X에서 1을 뺀다.
정수 X가 주어졌을 때, 연산 4개를 적절히 사용해서 1을 만들려고 한다. 연산을 사용하는 횟수의 최솟값을 출력하시오
예를 들어 정수가 26이면, 다음과 같이 계산해서 3번의 연산이 최솟값이다.
26 -> 25 -> 5 -> 1
[입력]
첫째 줄에 정수 X가 주어진다.(1 <= X <= 30,000)
입력 예시
26
[출력]
첫째 줄에 연산을 하는 횟수의 최솟값을 출력한다.
출력 예시
3
정답 풀이
''' 점화식
a[i] = i를 1로 만들기 위한 최소 연산 횟수
a[i] = min(a[i-1], a[i/2], a[i/3], a[i/5]) + 1
단, 1을 빼는 연산을 제외하고는 해당 수로 나누어 떨어질때에 한해 점화식 적용
'''
x = int(input())
d = [0] * 30001
# 보텀업 DP 진행
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])
효율적인 화폐 구성
[문제]
N가지 종류의 화폐가 있다. 이 화폐들의 개수를 최소한으로 이용해서 그 가치의 합이 M원이 되도록 하려고 한다. 이때 각 화폐는 몇 개라도 사용할 수 있으며, 사용한 화폐의 구성은 같지만 순서만 다른 것은 같은 경우로 구분한다.
예를 들어 2원, 3원 단위의 화폐가 있을 때는 15원을 만들기 위해 3원을 5개 사용하는 것이 가장 최소한의 화폐 개수이다.
[입력]
첫째 줄에 N,M이 주어진다(1<= N <= 100, 1<= M <= 10,000)
이후의 N개의 줄에는 각 화폐의 가치가 주어진다. 화폐의 가치는 10,000보다 작거나 같은 자연수이다.
입력 예시
2 15
2
3
[출력]
첫째 줄에 연산을 하는 횟수의 최솟값을 출력한다.
출력 예시
5
정답 풀이
n, m = list(map(int, input().split()))
money = []
for i in range(n):
money.append(int(input()))
d = [10001] * (m + 1)
# 보텀업 DP 진행
d[0] = 0
for k in money:
for j in range(k, m + 1):
if d[j - k] != 10001: # (i - k)원을 만드는 방법이 존재하면
d[j] = min(d[j], d[j - k] + 1)
# 계산된 결과 출력
if d[m] == 10001:
print(-1)
else:
print(d[m])
※ 문제 출처 : 이것이 취업을 위한 코딩 테스트다 with 파이썬 (나동빈 저).
※ 개인 풀이는 오류가 있을 수 있음.
'알고리즘 테스트 ⏲ > 이코테' 카테고리의 다른 글
[구현] 문자열 재정렬 풀이 (0) | 2022.07.03 |
---|---|
[이코테 Python] 최단 경로 알고리즘 (0) | 2022.01.19 |
[이코테 Python] 이진 탐색 알고리즘 문제 (0) | 2022.01.13 |
[이코테 Python] 정렬 알고리즘 문제 (0) | 2022.01.13 |
[이코테 Python] DFS / BFS 탐색 알고리즘 문제 (0) | 2022.01.13 |
소중한 공감 감사합니다