피보나치 수열 예시
def fib(n, memo):
if n <= 1:
return n
if memo[n] != -1:
return memo[n]
memo[n] = fib(n-1, memo) + fib(n-2, memo)
return memo[n]
n = 10
memo = [-1] * (n+1)
print(fib(n, memo))
def fib(n):
dp = [0] * (n+1)
dp[0], dp[1] = 0, 1
for i in range(2, n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
n = 10
print(fib(n))
LCS 문제는 동적 프로그래밍의 대표적인 예시 중 하나입니다. 두 문자열이 주어졌을 때, 두 문자열에 공통으로 존재하는 부분 수열 중 가장 긴 것의 길이를 찾는 문제입니다.
처음에는 문제를 이해하는 것부터 어려움을 겪었습니다. 부분 수열의 개념과 두 문자열에서 공통으로 존재하는 부분 수열을 찾는 방법이 직관적으로 와닿지가 않았다. (이걸 어떻게 재활용하라는거지??)
답을 보니, LCS(i, j)를 i번째 문자와 j번째 문자까지 고려했을 때의 LCS 길이라고 정의하고 시작했다.
def LCS(A, B):
n, m = len(A), len(B)
dp = [[0] * (m+1) for _ in range(n+1)]
for i in range(1, n+1):
for j in range(1, m+1):
if A[i-1] == B[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[n][m]
관련해서 간단한 예시코드인데, dp[i][j]
는 A의 i번째 문자와 B의 j번째 문자까지 고려했을 때의 LCS 길이.
코드의 동작 방식
1. A의 i번째 문자와 B의 j번째 문자가 같은 경우 (A[i-1] == B[j-1]
), 이 문자를 LCS에 포함시킬 수 있습니다. 따라서dp[i][j]
는dp[i-1][j-1]
에 1을 더한 값이다.
2. A의 i번째 문자와 B의 j번째 문자가 다른 경우, LCS에는 둘 중 하나의 문자만 포함될 수 있습니다. 이때는dp[i-1][j]
와dp[i][j-1]
중 더 큰 값을dp[i][j]
에 저장합니다.
이렇게 점화식을 통해 부분 문제의 해답을 재활용하면서 전체 문제의 해답을 구할 수 있습니다.
LCS 문제를 해결하고 나서도 다른 DP 문제에서 어려움을 겪었습니다. DP 문제마다 점화식을 세우는 방법이 달랐고, 답을 보더라도 이해하기까지 좀 시간이 소요됐다. DP는 이후에도 정말 많이 풀면서 문제 해결과정 프로세스?를 좀 머리 속 DB에 저장해야할 것 같았습니다.
특히 동적 프로그래밍하면서 가장 크게 느낀점은 수학적인 사고력이 필요하다는 것을 깊게 체감했습니다.
지속적으로 풀면서 수학적 사고를 늘리는 방법밖에 없을 것 같았다. 그래서 어려운 카테고리로 뽑히는건가 싶었다.
3주차에는 문제 풀이와 함께 4주차에 예정된 OS 개념 학습을 위해 C언어도 병행하여 공부할 계획입니다. C언어로 기본적인 자료구조를 구현해보면서 빠르게 따라잡을 수 있게 기반을 마련해놔야할 것 같습니다.