어제 문제 Climbing Stairs와 유사한 문제입니다!! 오늘도 공부해 봅시다.
You are given an integer array cost
where cost[i]
is the cost of ith
step on a staircase. Once you pay the cost, you can either climb one or two steps.
You can either start from the step with index 0
, or the step with index 1
.
Return the minimum cost to reach the top of the floor.
Example 1:
Input: cost = [10,15,20]
Output: 15
Explanation: You will start at index 1.
- Pay 15 and climb two steps to reach the top.
The total cost is 15.
Example 2:
Input: cost = [1,100,1,1,1,100,1,1,100,1]
Output: 6
Explanation: You will start at index 0.
- Pay 1 and climb two steps to reach index 2.
- Pay 1 and climb two steps to reach index 4.
- Pay 1 and climb two steps to reach index 6.
- Pay 1 and climb one step to reach index 7.
- Pay 1 and climb two steps to reach index 9.
- Pay 1 and climb one step to reach the top.
The total cost is 6.
Constraints:
2 <= cost.length <= 1000
0 <= cost[i] <= 999
계단이 있습니다. 한 번 올라갈 때마다 1 step 또는 2 steps 올라갈 수 있습니다. 문제에서 정수형 배열 cost가 주어지는데, cost[i]는 i번째 계단을 밟았을 때 지불해야 하는 비용입니다. 처음 출발은 index 0 또는 index 1중 한 곳에서 시작할 수 있습니다.
이 계단의 꼭대기에 도착하기 위해 지불해야 하는 최소 비용을 반환해야 합니다.
이 문제의 핵심은 계단을 오를때 1 step 또는 2 steps을 오를수 있다는 점입니다. 계단을 오르면서 해당 위치의 비용을 확인해야 합니다.
그럼 단계를 밟아가보며 문제를 풀어봅시다!
cost[i]는 i번째 계단을 밟았을 때 지불해야 하는 비용입니다. 그리고 계단을 올라갈 때 1칸 또는 2칸 올라갈 수 있습니다. 그리고 원하는 output은 꼭대기까지 오르는데 드는 최소 비용입니다.
이 내용을 구현하는데 시간 복잡도는 까지 가능합니다. 딱 떠오르는 경우는 일단 완전 탐색입니다.
모든 경우를 생각해보는 것이죠! 문제 분석에서 진행해 보겠습니다.
첫 번째 예시를 보시죠!
Input: cost = [10,15,20]
Output: 15
Explanation: You will start at index 1.
- Pay 15 and climb two steps to reach the top.
The total cost is 15.
여기서 말하는 꼭대기는 cost
배열의 마지막 인덱스 + 1 지점을 의미합니다. 즉, 배열의 마지막 계단을 넘어서는 지점이 우리가 도착해야 할 목표입니다. 이를 시각화해 보면, 계단을 오르기 위해 최소 비용을 계산할 때 각 단계에서 선택할 수 있는 경로가 보입니다.
계단은 1 step 또는 2 steps을 올라갈 수 있습니다. 이 뜻은 꼭대기의 1칸, 2칸전까지의 비용을 보면 꼭대기까지 가는데 알 수 있다는 것이죠. 이것을 통해 떠오르는 것이 있습니다. 바로 재귀 함수를 활용하는 것입니다. 다른 예시를 한 번 더 보겠습니다.
이전 예시와 동일하게 꼭대기로부터 1칸, 두 칸 전의 결과를 보고 또 그 1칸, 2칸으로부터 1칸 2칸을 보는식입니다. 또 하나 알 수 있는 것은 재귀를 하면서 중복적으로 계산이 되는 것이 있는 것을 알 수 있습니다.
그렇다는 것은 !! 바로 동적 프로그래밍의 메모이제이션을 사용하면 된다는 것을 알아차릴 수 있습니다.
재귀를 활용한 완전 탐색으로 시작했지만 결국 이 문제는 동적 프로그래밍으로 더 효율적으로 구현할 수 있다는 것을 알게 되었습니다.
재귀 함수를 활용하면 의 시간복잡도가 나옵니다. 하지만 동적 프로그래밍을 사용하면 의 시간 복잡도가 나오게 됩니다.
이제 코드 구현을 하러 갈까요?
코드 구현을 해보겠습니다.
Top-Down
class Solution(object):
# Top-Down
def minCostClimbingStairs(self, cost):
"""
:type cost: List[int]
:rtype: int
"""
memo = {}
def dp(n):
if n == 0 or n == 1:
return 0
if n in memo:
return memo[n]
memo[n] = min(dp(n - 1) + cost[n - 1], dp(n - 2) + cost[n - 2])
return memo[n]
return dp(len(cost))
코드 설명:
dp(n-1)
과 dp(n-2)
를 계산하고, 각 계단까지 도달하는 최소 비용을 비교하여 결과를 메모이제이션 테이블에 저장합니다.len(cost)
번째 계단에 도달하기 위한 최소 비용을 반환합니다.시간 복잡도:
n
)을 한 번만 계산합니다.O(1)
)에 min()
연산을 수행하므로 전체 시간복잡도는 O(n)
입니다.결과:
https://leetcode.com/problems/min-cost-climbing-stairs/submissions/1458009222
Top-Down으로 문제를 푼다면 Bottom-Up으로도 문제를 풀 수 있어야 합니다! 아래 Bottom-Up 코드입니다.
Bottom-Up
class Solution(object):
# # Bottom-Up
def minCostClimbingStairs(self, cost):
"""
:type cost: List[int]
:rtype: int
"""
n = len(cost)
dp = [0] * (n + 1)
for i in range(2, n + 1):
dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])
return dp[n]
코드 설명:
dp[i]
는 i
번째 계단에 도달하기 위한 최소 비용을 나타냅니다.dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])
dp
배열을 순차적으로 채웁니다.n
)에 도달하기 위한 최소 비용을 반환합니다.시간 복잡도:
n
개의 계단을 순차적으로 계산하므로 시간복잡도는 O(n)
입니다.결과:
https://leetcode.com/problems/min-cost-climbing-stairs/submissions/1458010504
이 문제는 동적 프로그래밍(DP)을 활용하여 효율적으로 해결할 수 있는 좋은 예제입니다. Top-Down 방식과 Bottom-Up 방식은 각기 다른 장단점을 가지고 있습니다.
이 문제를 통해 DP의 핵심 개념인 메모이제이션과 점화식 활용 방법을 다시 한 번 정리할 수 있었습니다.
쉽지 않은 DP, 반복 학습만이,, 살 길입니다!!
전체 코드는 다음 링크에서 확인할 수 있습니다.
읽어주셔서 감사합니다.
매일 발전하는 개발자가 될거야!