백준 문제 :
9095, 1912, 9461, 1699
DP 공부를 시작하는 마음가짐 🐳
◻️ 내 것이 될 때까지 물어보고 공부하고 정리하자
◻️ 파이써닉한 코드를 많이 보고 연구하자
◻️ 조급해 말고 나만 꽉 채우자
Dynamic Programming
의 약자로, Programming
은 '테이블 만든다'라는 뜻이다. 큰 문제와 작은 문제로 나눠서 푸는 계획법이며 접근방법은 3가지다:
DP는 분할정복(divide and conquer)과 다르다. 분할정복은 큰 문제 해결하기 어려워서 작은 문제로 나눠서 푸는 방법이다. 분할정복과 달리 DP는 작은 문제에 중복이 없으며 계산한 작은 부분문제들을 이용해 풀어나간다.
모든 작은 문제들은 한번만 풀어야 하며 정답을 구한 작은 문제는 어딘가 보관한다. 보관한 작은 문제보다 더 큰 문제를 풀어나가야 할 때 똑같은 작은 문제가 나타나면 보관한 작은 문제의 결과값을 이용하는 방법이다.
작은 문제와 작은 문제의 결과값을 이용한 풀이기 떄문에 DP는 해당 조건에 쓴다:
요약 : DP는 작은 문제들이 중복되며, 그의 결과값이 계속 같을 때 사용하는 계획법이다.
한번 계산한 작은 문제들의 결과값을 저장해놓고 다시 사용하는 방법이다.
예시 코드 1:
def fib(n, memo):
if memo[n] != null:
return memo[n]
if n==1 or n==2:
result = 1
else:
result = fib(n-1)+fib(n-2)
memo[n] = result
return result
예시 코드 2:
def fib(n):
memo[0] = 1
memo[1] = 1
if n < 2:
return memo[n]
for i in range(2, n+1):
memo[i] = memo[i-2]+memo[i-1]
return memo[n]
if __name__ == '__main__':
n = int(sys.stdin.readline())
memo = [0 for i in range(n+2)]
print(fib(n))
0,1번째 수열을 항상 1이므로 저장한다. 2번째 수열을 구할 때 배열에 저장된 이 값을 이용하며,2번째 수열의 결과값도 배열에 저장한다. n
번째 수열을 구할 때까지 반복한다.
2A) 바텀-업(bottom-up):
작은 문제부터 구해나가는 방법이다. for문, 재귀를 이용해 처음값부터 다음값을 계산해 나간다.
def fib(n):
if n <= 1:
return n
fir = 0
sec = 0
for i in range(0,n-1):
next = fir+sec
fir = sec
sec = next
return next
if __name__ == '__main__':
n = int(sys.stdin.readline())
print(fib(n))
2A) 탑-다운(top-down):
재귀함수로 구현하는 경우 대부분 탑-다운이라고 생각하면 된다. 큰 문제를 풀 때 작은 문제가 아직 안 풀렸다면 작은 문제를 해결하는 방법이다. 함수 호출을 줄이기 위해 앞서 말했던 메모이제이션을 사용한다.
def fib(n):
if memo[n] > 0:
return memo[n]
if n <= 1:
memo[n] = n
return memo[n]
else:
memo[n] = fib(n-1)+fib(n-2)
return memo[n]
if __name__ == '__main__':
memo = [0 for i in range(100)]
n = int(sys.stdin.readline())
print(fib(n))
계산한 부분과 계산하지 않은 부분을 구분해야 한다. 계산하지 않은 값은 초기값 그대로며 계산한 값은 바뀌었기 때문에 그렇게 차이점을 둔다. DP에서 계산한 값의 범위를 대략적으로 알아내서 그 범위에 있지 않은 값으로 초기화를 해준다. 계산된 값이 0일 수 있는 DP의 경우에는 보톤 -1(무한대값, INT_MAX)을 사용하고 계산된 값이 0일 수 없는 경우에는 전역 변수를 선언한다.
시간 제한 지나서야 패턴을 발견했다.
arr[i] = arr[i-1] + arr[i-2] + arr[i-3]
testcase=int(input())
dp=[ 0 for i in range(12)]
dp[1]=1
dp[2]=2
dp[3]=4
for i in range(4,12):
dp[i]=dp[i-1]+dp[i-2]+dp[i-3]
for i in range(testcase):
a=int(input())
print(dp[a])
출처 - pro-jy
풀이:
dp[n]
으로 dp
리스트에 값을 넣는다dp[0]~[3]
까지 값이 넣어져 있으니 dp[4]
부터 값을 넣기 위해 range를 4~12로 설정한다dp[i]=dp[i-1]+dp[i-2]+dp[i-3]
패턴으로 dp[i]
값을 넣는다testcase
를 range로 설정해 testcase
의 개수만큼 a
변수를 입력하고, print(dp[a])
작업을 반복한다배운점:
1. 제로 리스트로 dp 테이블을 먼저 만들기
2.testcase
를 range로 설정해서 입력 변수 제한 두기!
문제풀이 체크리스트
◼️ 시간 제한 지났음에도 문제 터치 못함
◻️ 시간 제한 후 코드 완성
◻️ 코드 미완성
◻️ 코드 완성 - 에러
◻️ 코드 완성 - 정답
import sys
input = sys.stdin.readline
n = int(input())
a = list(map(int, input().split()))
d = [[] for _ in range(n)]
d[0] = a[0]
for i in range(1, n):
d[i] = max(a[i], d[i-1] + a[i])
print(max(d))
출처 - chldkato
풀이:
1. n
변수를 선언해 정수 개수 입력한다
2. a
변수에 n
개의 정수로 이루어진 수열 입력한다
3. n
개의 리스트를 만든다
4. d[0] = a[0]
로 만들어서 똑같이 만든다
5. d[0]
은 이미 값이 들어있기 때문에 range(1, n)
으로 설정하고 max()
함수를 이용해 a[i]
와 d[i-1] + a[i]
중 더 큰 숫자를 d[i]
에 대입한다
6. 그리고 리스트 안에 있는 가장 큰 숫자를 출력한다
배운점:
1.for _ in range(n)
보면i
대신_
을 써서 iterator 변수 없이 for문을 선언한다.
2. a[i] = d[i-1] + a[i]max() : max(iterable, *iterables, key, default)
문제풀이 체크리스트
◼️ 시간 제한 지났음에도 문제 터치 못함
◻️ 시간 제한 후 코드 완성
◻️ 코드 미완성
◻️ 코드 완성 - 에러
◻️ 코드 완성 - 정답
tc = int(input())
dp = [0 for _ in range(n)]
dp[0] = 0
dp[1] = 1
dp[2] = 1
dp[3] = 1
dp[4] = 2
dp[5] = 3
for i in range(6, n):
dp.append(dp[i-1] + dp[i-5])
for _ in range(tc):
n = int(input())
print(dp[n])
런타임 에러
가 떴다. 확인해보니 NameError: name 'n' is not defined
가 떴다. 역시 함수를 만들었어야 했나...
나는 아마 이런 코드를 구현하고 싶었던 것 같다✨ :
T = int(input())
Ns =[0,1, 1, 1, 2, 2, 3, 4, 5, 7, 9]
for _ in range(T):
N = int(input())
if N < len(Ns):
print(Ns[N])
else:
for i in range(len(Ns), N+1):
Ns.append(Ns[i-1]+Ns[i-5])
print(Ns[N])
출처 - kils-log-of-develop
import sys
n = int(sys.stdin.readline())
memo = {1:1,2:1,3:1,4:2,5:2}
def find_result(number : int) -> int:
if number in memo:
return memo[number]
memo[number] = find_result(number-1) + find_result(number-5)
return memo[number]
for _ in range(n):
num = int(sys.stdin.readline())
print(find_result(num))
출처 - inspirit941
이 코드의 흥미로운 점은 리스트가 아닌 딕셔너리로 메모이제이션을 했다.
문제풀이 체크리스트
◻️ 시간 제한 지났음에도 문제 터치 못함
◻️ 시간 제한 후 코드 완성
◻️ 코드 미완성
◼️ 코드 완성 - 에러
◻️ 코드 완성 - 정답
n = int(input())
dp = [0 for i in range(n + 1)]
square = [i * i for i in range(1, 317)]
for i in range(1, n + 1):
s = []
for j in square:
if j > i:
break
s.append(dp[i - j])
dp[i] = min(s) + 1
print(dp[n])
출처 - pacific-ocean
n
변수를 선언한다n+1
개의 0
로 이루어진 제로 리스트를 만든다square
변수는 1-317(317^2=100489)
로 제곱 숫자를 만든다s
라는 빈 리스트를 선언하고, square
변수 안에 각 제곱 숫자를 순회하며 square
제곱 숫자 요소가 i
보다 더 클 경우 break
한다. 만약 square
제곱 숫자 요소가 i
보다 작으면 s
에 dp[i - j]
를 추가한다dp[i]
는 s
에서 가장 적은 수와 1을 더한다. ** 왜 1을 더하는지 이해가 안된다 ㅠㅠ
import sys
input = sys.stdin.readline
n = int(input())
dp = [100000 for i in range(100001)]
dp[0] = 0
dp[1] = 1
dp[2] = 2
dp[3] = 3
i = 1
while(i**2 < n+1):
dp[i**2] = 1
i+=1
for i in range(2,n+1):
j = 2
while(j*j <= i):
dp[i] = min(dp[i],dp[i-j*j]+1)
j+=1
print(dp[n])
출처 - dev-wd
문제풀이 체크리스트
◼️ 시간 제한 지났음에도 문제 터치 못함
◻️ 시간 제한 후 코드 완성
◻️ 코드 미완성
◻️ 코드 완성 - 에러
◻️ 코드 완성 - 정답
Reference
https://www.acmicpc.net/
https://www.youtube.com/watch?v=vYquumk4nWw&t=571s
https://www.hackerearth.com/practice/algorithms/dynamic-programming/introduction-to-dynamic-programming-1/tutorial/
https://www.quora.com/What-does-_-in-Python-mean-in-a-for-loop
https://www.programiz.com/python-programming/methods/built-in/max
https://coding-all.tistory.com/2
https://galid1.tistory.com/507
https://pro-jy.tistory.com/6
https://chldkato.tistory.com/122
https://kils-log-of-develop.tistory.com/411
https://inspirit941.tistory.com/entry/Python-%EB%B0%B1%EC%A4%80-9461-%ED%8C%8C%EB%8F%84%EB%B0%98-%EC%88%98%EC%97%B4
https://dev-wd.github.io/algorithm/backjooon1669/
https://pacific-ocean.tistory.com/205?category=810810