이전시간에서는 Dynmaic programming(DP)에 대해서 배워보았으니 문제를 풀어보는 시간에 대해서 가져보겠습니다.
DP문제를 풀기위한 Framework에 대해서 이는 왠만하게 모든 방법에서 풀수 있기때문에 단계별로 이해하면서 문제를 풀어보도록 하겠습니다.
아래그림처럼 처음 풀 문제는 Climbing Staris의 문제입니다. 이 문제는 top-down(recursive)로 풀수 있는데요
이 문제의 경우에서는 한글말로 "계단오르기"문제로써 간편하게 풀수 있는 문제입니다.
문제 : n step을 통해서 정상에 올라갈수 있는데 정상에 오르기 위해서는 몇가지의 방법을 통해서 올라갈수 있는지??
조건 : 한번 올라갈때마다 반듯이 1,2칸만 갈 수 있음.
예시 : n=3이라면 (1,1,1),(1,2),(2,1)로 총 3가지의 방법으로 올라갈수 있음.
이 문제를 풀기 위해서는 우리가 이전에 공부하였던 DP를 활용하여 문제를 풀어 봅시다.
DP를 적용하기 위해서 state라는 개념이 있는데요.
state의 경우는 지금의 현제 상태에 대해서 설명할수 있는 변수의 집합이라고 됩니다. 위의 그림처럼 계단 오르기를 할때 step이 3이라고 하면 3가지의 방법들로 풀수 있는데요 (ex) n=3이라면 (1,1,1),(1,2),(2,1))
이때 집합들의 값들에 있는 변수들을 state variables라고 부릅니다.
이때 state variable에서 연관(relevant)이 있는 요소는 하나만 있는데 이 relevant 에 대해서는 다음에 설명하도록 하겠습니다 .
이러한 문제를 DP의 문제는 3가지의 조건을 반듯이 가지는데요 이러한 Framework에 대해서 알아보도록 하겠습니다.
Framework
- function이나 data structure들은 모든 state 마다 계산이 되어져야 합니다.
위의 문제로 예시로 들어보았을떄 dp(i)를 function으로 잡았을때 이 dp(i)는 $i^{th}$번째에 방법들에 대해서 나와야 합니다. - 점화식/재귀식 (recurrence relation)은 다른state에 이행되어진다.
마찬가지로 위의 문제처럼 dp(30)으로 간다고 하였을떄 수많은 state variables이 집합으로 되어있을겁니다. 이떄 dp(30)은 이전의 29th과 28th에 도착되어졌을때 1,2의 계단만 올라가면 되는 문제로 풀수 있습니다.
따라서 이론적으로 recurrence relation로 풀수 있으므로 dp(i) = dp(i-1) + dp(i-2)로 풀어집니다 - 초기 값들은 recurrence relation은 무한하게 되어지지 않도록 방지하는 역할을 합니다.
dp(i) = dp(i-1) + dp(i-2)라는 수식 자체는 반복하다보면 negative값까지 가게 되어서 무한하게 수식이 되어집니다.
그렇기 때문에 이를 방지할 수 있는 base가 되는 값을 가져야 하며 위의 문제에서는 dp(1)=1, dp(2)=2라는 초기 값을 설정이 되어짐을 볼수 있습니다.
이를 통해서 negative로 가는 것을 막을수 있으며 위의값들을 풀어낼 수 있습니다.
Example Implementations
그렇다면 2가지의 문제로 접근해서 풀어보도로 하겠습니다.
Top-down
class Solution:
def climbStaris(slef, n: int) -> int:
def dp(i):
if <= 2:
return i
return dp(i-1) + dp(i-2)
return dp(n)
위의 방법은 $O(2^n)$의 방법으로 구성이 되어지지만 top-down 방식의 기본인 memoization이 없는 것을 볼 수 있습니다. 이 방법의 time complexity를 줄이기 위해서 memoization를 사용하게 되면 아래와 같습니다.
class Solution:
def climbStairs(self, n: int) -> int:
def dp(i):
if i <= 2:
return i
if i not in memo:
memo[i] = dp(i - 1) + dp(i - 2)
return memo[i]
memo = {}
return dp(n)
이렇게 되면 $O(n)$로 줄어드는 것을 볼 수 있다.
bottom-up
이 방법을 적용해보자
class Solution:
def climbStairs(self, n: int) -> int:
if n == 1:
return 1
# An array that represents the answer to the problem for a given state
dp = [0] * (n + 1)
dp[1] = 1 # Base cases
dp[2] = 2 # Base cases
for i in range(3, n + 1):
dp[i] = dp[i - 1] + dp[i - 2] # Recurrence relation
return dp[n]
위와 같은 방법으로 보았을떄 각자의 장단점이 있음으로 top-down, bootom-up의 방법을 적당히 사용하면된다.