1. Fibbonacci 수 계산하기
2. 계단 오르는 경우의 수
3. 도미노 타일링
<점화식이 여러 작은 문제의 합으로 표현되는 경우>
-> 2 * N 크기의 벽을 2 * 1, 1 * 2 크기의 타일로 채울려고 하는 경우
1) subproblem
dp[i-1]
dp[i-2]
2) 점화식
dp[i] = dp[i-1] + dp[i-2]
3) dp 테이블 채우기 - tabulation
4. 최대합 계산하기
5. zig-zag 수열
6. LCS(Longest Common Subsequence) 계산하기
문제 정의
대표적인 DP 문제로 두 문자열 X, Y의 가장 긴 공통 부문자열을 찾는 문제임.
활용
DNA 시퀀스가 서로 얼마나 가까운 유전자인지 LCS 길이를 통해 알 수 있다. -> 유전 공학에 쓰인다고 교수님께서 말씀해주셨다
어떻게 풀까?
두 문자열이 주어지고 두 문자열의 공통 부문자열을 구해야한다.
- X = A B C B D A B
- Y = B D C A B A
1) 공통 부문자열을 구하기 위해서 먼저 해를 분석하여 부문제로 분할한다.
LCS(Xi, Yj)를 구하는 것이 부문제가 됨.
맨 끝의 두 문자가 다른 경우와 맨 끝의 두 문자가 같은 경우 두가지로 나눌 수 있다.
if X7 != Y6:
LCS(X7, Y6)은 LCS(X7, Y5)나 LCS(X6, Y6) 중 더 긴 것이다.
맨 끝의 두 문자가 다른 경우에는 둘 중 어느 것을 고른 게 최장 문자열일지 모르기 때문이다.
if X7 == Y6:
LCS(X7, Y6)은 LC(X6, Y5) + 1이다.
두 문자가 같기 때문에 뭘 고르든 최장 문자열이기 때문이다.
코드 구현
X = list(input())
Y = list(input())
def LCS(X, Y):
n = len(X)
m = len(Y)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(1, m+1):
for j in range(1, n+1):
if X[j-1] != Y[i-1]:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
else:
dp[i][j] = dp[i-1][j-1] + 1
print(dp[m][n])
LCS(X, Y)
7. 편집거리문제 (Edit distance problem)
문제 정의
문자열 X의 어떤 문자를 지우거나(delete) 새로운 문자를 삽입해서(insert) 다른 문자열 Y를 만들 수 있다. 따라서 편집 거리문제는 최소 횟수의 삽입 또는 삭제 연산으로 X를 Y로 변경할 수 있는지를 계산하는 문제이다.
어떻게 풀까?
X = HUMAN
Y = CHIMPANZEE
요 X가 Y로 바뀌는 것을 삽입, 삭제, 교체 연산으로 표현하기~
ex) HUMAN -> HMAN -> CHMAN -> CHIMAN -> CHMPAN -> CHIMPANZEE
삽입 연산 - Wins(z) = X 문자열에 문자 z를 삽입하는 비용
삭제 연산 - Wdel(z) = X 문자열에서 문자 z를 삭제하는 비용
교체 연산 - Wsub(z) = X 문자열에서 문자 ~를 z로 교체하는 비용
dp 테이블로 표현하면?
dp[i][j] : Xi -> Yj의 최소 비용
dp[n][m] : Xn -> Ym의 최소 비용
dp[i][0] : Xi -> Y0의 최소 비용 - 모두 삭제 비용
dp[0][j] : X0 -> Yj의 최소 비용 - 모두 삽입 비용
이런 식으로 dp테이블을 나타낼 수 있다
dp테이블을 채우기 전 일단 문제를 쪼개야 한다.
먼저 해를 분석하여 부문제로 분할한다!
dp[i][j]의 값은 Xi -> Yj의 최소 비용인데
이를 구하기 위해서는
1 - Wdel(Xi) + dp[i-1][j](Xi-1 -> Yj의 최소비용)
2 - dp[i][j-1](Xi -> Yj-1의 최소비용) + Wins(Yj)
3 - dp[i-1][j-1] + Wsub(Xi, Yj)
이렇게 분할해서 생각할 수 있고
이 중 가장 작은 값이 dp[i][j]의 값이 된다. -> min(1, 2, 3)
이걸 for문을 돌려서 dp테이블을 채우고
이를 바탕으로 최종적으로 구하고 싶은 값을 구하면 됨!
8. 괄호 만들기
문제 정의
행렬의 곱셈을 할 때 어떤 순서로 하느냐에 따라 기본 연산의 횟수가 크게 달라진다. 따라서 행렬의 곱셈을 하기 위해 기본 연산의 횟수가 최소가 되도록 괄호를 쳐서 순서를 정하는 것이 중요하다. 이 문제는 행렬의 곱셈이 최소 비용이 되도록 괄호를 치는 방법을 계산하는 것이 목적이다.
어떻게 풀까?
이 문제를 풀기 위해서 해를 살펴보고 작은 부문제로 나누어 보자.
M1 * M2 * M3 * M4 이런 행렬의 곱셈이 있다면,
괄호의 경우의 수는 다음과 같은 5가지 이다.
1 - M1 * ((M2 * M3) * M4)
2 - M1 * (M2 * (M3 * M4))
3 - ((M1 * M2) * (M3 * M4))
4 - ((M1 * M2) * M3) * M4
5 - (M1 * (M2 * M3)) * M4
맨 마지막의 곱셈이 어느 것이냐에 따라 분류해볼 수 있는데,
예를 들어 맨 마지막 곱셈이 첫번째 곱하기인 경우
M1을 계산하는 비용이 dp[1][1]이고,
(M2 * M3 * M4)을 계산하는 비용은 dp[2][4]이다.
그리고 이 둘을 마지막에 곱하는 비용이 p1 * p2 * p5이다.
이와 같이 맨 마지막 곱셈이 두번째 곱하기인 경우,
맨 마지막 곱셈이 세번째 곱하기인 경우도 마찬가지로 생각해 볼 수 있다.
이렇게 곱셈을 앞에서부터 차례대로 보면서
작은 부문제로 만들어 줬다.
이를 일반화 해서 써보도록 하겠음
dp[i][j] = [mi * ... * mk] * [mk+1 *....*mj]
dp[i][j] = min(dp[i][k] + dp[k+1][j] + pi * pk+1 * pj+1들(i<= k < j))
위의 식을 좀 설명해 보자면,,,
k는 i부터 시작해서 j까지 커지는데,
k와 k+1 사이에 곱하기를 하나씩 옮겨가는 것
곱하기를 기준으로 쪼개서 계산하여 필요한 비용의 경우의 수를 다 구하고
그들 중의 최소값을 취하는 것이다!
쪼갰기 때문에 dp테이블에 모든 값들은 저장되어 있기 때문에 빠른 연산이 가능함
import math
def matrix_mult():
for l in range(n-2, -1, -1):
for i in range(0, l+1):
j = i + (n-1-l)
C[i][j] = math.inf # math module에서 제공하는 매우 큰 정수
for k in range(i, j):
cost = C[i][k] + C[k+1][j] + P[i]*P[k+1]*P[j+1]
if C[i][j] > cost:
C[i][j] = cost
return C[0][n-1]
n = int(input()) # n = 행렬 갯수, M_0부터 행렬시작임을 주의!
matrix = [list(map(int, input().split())) for _ in range(n)]
P = [matrix[0][0]] + [x[1] for x in matrix] # M_i = p_i x p_{i+1}
C = [[0]*n for _ in range(n)] # 비용을 저장할 2차원 리스트 C 초기화
min_cost = matrix_mult()
print(min_cost)
모든 테스트 케이스 통과 되는지는 모르겠음,,
왜냐면 일단 코드트리에서는 안돌아가는데
코드트리 테케가 좀 이상해서?
백준 돌려보는 중 -> 맞았당
9. LIS 최장 증가 부수열
문제 정의
가장 긴 증가 부수열을 찾는 문제이다. 지그재그 수열처럼 앞의 수열들을 dp로 저장해 가장 긴 증가 부수열을 찾을 수 있다.
예를 들어 A = [2, 8, 5, 10, 18, 13, 20, 4] 인 경우에 2, 5, 10, 18, 20과 8, 10, 13 모두 증가 부수열이지만 앞의 부수열의 길이가 더 길기 때문에 앞의 것이 최장 증가 부수열이다.
어떻게 풀까?
앞에서부터 차례로 살펴보면서 만약 j < i 일 때 A[j] < A[i] 이면 LIS[i] = max(LIS[i], LIS[j]+1) -> 만약 LIS[i]보다 LIS[j]+1가 크면 LIS[i]는 LIS[j]+1
코드 구현
n = int(input())
A = list(map(int, input().split()))
def make_LIS(n, A):
LIS = [0 for _ in range(n)]
LIS[0] = 1
for i in range(1, n):
LIS[i] = 1
for j in range(i):
if A[j] < A[i]:
LIS[i] = max(LIS[i], LIS[j]+1)
return max(LIS)
print(make_LIS(n, A))
코드트리 문제 -최대 점프 횟수
'CS > 알고리즘' 카테고리의 다른 글
[자료구조] HashMap (1) | 2024.04.15 |
---|---|
[알고리즘] 그리디 알고리즘이란? (0) | 2024.03.27 |
[알고리즘] 그래프란? & 탐색 알고리즘이란? (1) | 2024.03.04 |
[알고리즘] 완전탐색이란? (0) | 2024.02.14 |
[알고리즘] 동적계획법(Dynamic Programming)이란? (0) | 2024.02.14 |