동전 교환과 최소 비용 계단
coin-change와 min-cost-climbing-stairs의 점화식을 세우고 올바른 DP 방향을 선택한 뒤 표를 직접 따라가 봅니다.
동전 교환과 최소 비용 계단은(는) CoddyKit의 무료 DSA Interview Prep 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 DSA Interview Prep 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. DSA Interview Prep 강의에는 총 4개의 강의가 포함되어 있습니다.
동전 교환: 문제
동전 교환(LeetCode #322)에서는 동전의 액면가와 목표 금액이 주어집니다. 정확한 금액을 만들기 위해 필요한 최소 동전 수를 구해야 합니다. 각 액면가의 동전을 무제한으로 사용할 수 있습니다. 이는 전형적인 무제한 배낭 문제의 변형으로, 각 항목(동전)을 원하는 횟수만큼 사용할 수 있습니다. 처음부터 점화식을 세우는 능력을 평가하기 때문에 가장 중요한 DP 문제 중 하나입니다.
# Problem examples:
# coins=[1,5,6,9], amount=11 -> 2 (5+6 or 2+9? no: 5+6=11 YES)
# coins=[2], amount=3 -> -1 (impossible)
# coins=[1,2,5], amount=11 -> 3 (5+5+1)
# coins=[186,419,83,408], amount=6249 -> 20
# Key choices:
# - Try each coin denomination at each step
# - Minimum coins = 1 + minimum(coins to make amount - coin)
# - If amount < 0: impossible
# - If amount = 0: done (0 coins)
print('Coin change: unbounded knapsack, find minimum count')동전 교환: 점화식 도출
dp[i]를 금액 i를 만드는 데 필요한 최소 동전 수로 정의합니다. 각 금액 i에 대해 동전 c를 하나씩 사용해 봅니다. i >= c이면 dp[i] = min(dp[i], 1 + dp[i-c])입니다. 여기서 1은 방금 사용한 동전을 나타내고, dp[i-c]는 남은 금액에 대한 최적 해법입니다. 이는 동전이 무한히 많다고 가정합니다. 기본 경우는 dp[0] = 0입니다. 나머지 항목은 아직 만들 수 없다는 의미로 무한대에 해당하도록 초기화합니다.
def coin_change(coins, amount):
# dp[i] = min coins to make amount i
dp = [float('inf')] * (amount + 1)
dp[0] = 0 # base: 0 coins for amount 0
for i in range(1, amount + 1):
for coin in coins:
if i >= coin and dp[i - coin] != float('inf'):
dp[i] = min(dp[i], 1 + dp[i - coin])
return dp[amount] if dp[amount] != float('inf') else -1
print(coin_change([1, 5, 6, 9], 11)) # 2
print(coin_change([2], 3)) # -1
print(coin_change([1, 2, 5], 11)) # 3
# Trace dp for coins=[1,5] amount=6:
# dp[0]=0, dp[1]=1, dp[2]=2, dp[3]=3, dp[4]=4, dp[5]=1, dp[6]=2동전 교환: 탐욕적 방법이 실패하는 이유
탐욕적 방법(들어맞는 동전 중 항상 가장 큰 것을 고르는 방법)은 동전 교환에서 실패합니다. 예를 들어 동전이 1, 3, 4이고 금액이 6인 경우, 탐욕적 방법은 4를 고른 다음 1+1을 골라 동전 3개를 사용합니다. 최적 해법은 3+3으로 동전 2개를 사용하는 것입니다. 탐욕적 방법이 표준 액면가(1, 5, 10, 25센트)에서는 작동하는 이유는 이 액면가들이 우연히 탐욕적 선택의 성질을 만족하기 때문입니다. 그러나 임의의 동전 집합에는 DP가 필요합니다. 이는 면접에서 자주 다루는 핵심으로, 탐욕적 방법이 실패한다고 말하고 그 이유를 설명하면 뛰어난 분석적 사고를 보여 줄 수 있습니다.
# Greedy failure example:
# coins=[1,3,4], amount=6
# Greedy: 4 (rem=2), 1 (rem=1), 1 (rem=0) -> 3 coins
# Optimal: 3 (rem=3), 3 (rem=0) -> 2 coins
def coin_change_greedy_wrong(coins, amount):
coins_sorted = sorted(coins, reverse=True)
count = 0
for coin in coins_sorted:
while amount >= coin:
amount -= coin
count += 1
return count if amount == 0 else -1
print('Greedy:', coin_change_greedy_wrong([1,3,4], 6)) # 3 (WRONG)
print('DP: ', coin_change([1,3,4], 6)) # 2 (CORRECT)동전 교환 II: 방법의 수
동전 교환 II(LeetCode #518)는 금액을 만드는 방법의 수를 묻습니다(최소 개수가 아닙니다). 점화식도 달라져서 최솟값 대신 합을 사용합니다. 각 동전에 대해 dp[i] += dp[i-coin]을 적용합니다. 표를 채우는 순서가 중요합니다. 각 조합을 한 번만 세려면 동전 반복문을 바깥쪽에 두고 금액 반복문을 안쪽에 둡니다. 반복문의 순서를 바꾸면 조합이 아니라 순열을 세게 됩니다(이는 다른 문제입니다).
def coin_change_ii(coins, amount):
# dp[i] = number of ways to make amount i
dp = [0] * (amount + 1)
dp[0] = 1 # one way to make amount 0: use no coins
# Outer loop: coins -- ensures each coin type processed once
for coin in coins:
# Inner loop: amounts
for i in range(coin, amount + 1):
dp[i] += dp[i - coin]
return dp[amount]
print(coin_change_ii([1, 2, 5], 5)) # 4: [1,1,1,1,1],[1,1,1,2],[1,2,2],[5]
print(coin_change_ii([2], 3)) # 0: impossible
print(coin_change_ii([10], 10)) # 1
# Key: coin outer, amount inner = COMBINATIONS (unordered)
# Reverse (amount outer, coin inner) = PERMUTATIONS (ordered)최소 비용 계단 오르기: 문제
최소 비용 계단 오르기(LeetCode #746)에서는 각 계단에 비용이 있는 계단이 주어집니다. 한 번에 1계단 또는 2계단을 오를 수 있습니다. 마지막 계단에서 한 칸 더 간 꼭대기에 도달하는 최소 비용을 구해야 합니다. 0번 계단이나 1번 계단에서 비용 없이 시작할 수 있습니다. 이 문제는 계단 오르기의 점화식과 동전 교환의 비용 최소화 패턴을 우아하게 결합하므로 두 문제를 연결하는 자연스러운 다리 역할을 합니다.
# cost = [10, 15, 20]
# Pay cost[i] to leave step i
# You can step to i+1 or i+2
# Goal: reach top (index 3) with minimum cost
# Path options:
# Start at 0: cost 10, go to 2: cost 20, done -> 30
# Start at 1: cost 15, go to 3: done -> 15 <- OPTIMAL
# Start at 0: cost 10, go to 1: cost 15 -> 25
cost = [10, 15, 20]
# Optimal: start at step 1, pay 15, jump to top -> cost = 15
print('Expected:', 15)최소 비용 계단 오르기: 점화식
dp[i]를 i번 계단에 도달하는 최소 비용으로 정의합니다. i번 계단에는 i-1번 계단에서 오면서 cost[i-1]을 지불하거나, i-2번 계단에서 오면서 cost[i-2]를 지불해 도달합니다. 따라서 dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])입니다. 기본 경우는 dp[0] = 0(계단 앞에서 무료로 시작)과 dp[1] = 0(1번 계단에서도 무료로 시작 가능)입니다. 정답은 dp[n]이며, n은 비용 배열의 길이입니다.
def min_cost_climbing_stairs(cost):
n = len(cost)
# dp[i] = minimum cost to reach step i
# Steps 0 to n; step n is the top (goal)
dp = [0] * (n + 1)
# dp[0] = 0 (free to start here)
# dp[1] = 0 (free to start here)
for i in range(2, n + 1):
dp[i] = min(dp[i-1] + cost[i-1], # step from i-1
dp[i-2] + cost[i-2]) # jump from i-2
return dp[n]
print(min_cost_climbing_stairs([10, 15, 20])) # 15
print(min_cost_climbing_stairs([1,100,1,1,1,100,1,1,100,1])) # 6최소 비용 계단 오르기: 공간 최적화
dp[i]는 dp[i-1]과 dp[i-2]에만 의존하므로 피보나치와 마찬가지로 두 변수를 사용해 공간을 O(1)로 줄일 수 있습니다. 배열을 prev2와 prev1로 바꾸고, 각 단계에서 두 값을 갱신합니다. 이는 면접관이 O(n) 표 해법을 제시한 뒤 기대하는 전형적인 한 줄 최적화입니다. 마지막 두 값만 필요하므로 이를 O(1) 공간으로 줄일 수 있다고 항상 먼저 언급하세요.
def min_cost_optimised(cost):
n = len(cost)
prev2, prev1 = 0, 0 # dp[0] and dp[1]
for i in range(2, n + 1):
curr = min(prev1 + cost[i-1], prev2 + cost[i-2])
prev2, prev1 = prev1, curr
return prev1
print(min_cost_optimised([10, 15, 20])) # 15
print(min_cost_optimised([1,100,1,1,1,100,1,1,100,1])) # 6
# Alternative: directly use cost array as rolling storage
def min_cost_v2(cost):
n = len(cost)
for i in range(2, n):
cost[i] += min(cost[i-1], cost[i-2])
return min(cost[-1], cost[-2])
from copy import deepcopy
cost_test = [10,15,20]
print(min_cost_v2(deepcopy(cost_test))) # 15대안적인 DP 구성
일부 문제에는 유효한 DP 구성이 여러 가지 있습니다. 최소 비용 계단 오르기에서는 dp[i]를 i번 계단을 떠나는(LEAVE) 최소 비용으로 정의할 수 있습니다(비용 cost[i]를 지불하고 i+1번 또는 i+2번 계단으로 가는 방법을 선택합니다). 그러면 dp[i] = cost[i] + min(dp[i+1], dp[i+2])가 되며, 오른쪽에서 왼쪽으로 표를 채웁니다. 정답은 min(dp[0], dp[1])입니다. 두 구성 모두 올바릅니다. 어떤 구성을 선택했는지와 그 이유를 설명하는 연습을 하세요. 이는 DP에 능숙하다는 것을 보여 줍니다.
def min_cost_alternative(cost):
n = len(cost)
# dp[i] = min cost when starting FROM step i
# Fill right to left
dp = cost[:] + [0] # dp[n] = 0 (already at top)
for i in range(n - 1, -1, -1):
# Pay cost[i], then choose i+1 or i+2
if i + 2 <= n:
dp[i] = cost[i] + min(dp[i+1], dp[i+2])
else:
dp[i] = cost[i] + dp[i+1]
# Can start at step 0 or step 1
return min(dp[0], dp[1])
print(min_cost_alternative([10, 15, 20])) # 15
print(min_cost_alternative([1,100,1,1,1,100,1,1,100,1])) # 6동전 교환과 계단 오르기 연결하기
동전 교환과 최소 비용 계단 오르기는 모두 같은 DP 패턴의 사례입니다. 각 단계에서 유한한 선택지 중 하나를 고르고, 선택의 순서 전체에 대한 목표를 최적화합니다. 차이는 표면적입니다. 동전 교환은 개수를 추적하여 동전마다 1을 더하고, 계단 오르기는 비용을 추적하여 각 단계의 비용을 더합니다. 이 공통 구조를 인식하면 새로운 DP 문제를 익숙한 틀에 대응시켜 해결할 수 있습니다.
# Shared pattern:
# dp[state] = optimise(dp[prev_state_1] + cost_1,
# dp[prev_state_2] + cost_2, ...)
# Coin change: dp[amount] = min(1 + dp[amount - coin] for coin in coins)
# Min stair: dp[step] = min(cost[step-1]+dp[step-1], cost[step-2]+dp[step-2])
# Max path sum: dp[cell] = max(dp[top], dp[left]) + grid[cell]
# House robber: dp[house] = max(dp[house-1], dp[house-2] + value[house])
# All four are the SAME pattern with different:
# - State representation
# - Number of choices per state
# - Objective (min/max)
# - Transition cost
print('DP pattern: state + choices + objective + cost = template')최소 개수의 완전 제곱수
완전 제곱수(LeetCode #279)는 합이 n이 되는 완전 제곱수(1, 4, 9, 16, ...)의 최소 개수를 묻습니다. 이는 동전이 완전 제곱수인 동전 교환 문제와 정확히 같습니다. n 이하의 모든 완전 제곱수를 생성한 뒤 동전 교환을 실행합니다. DP는 O(n * sqrt(n)) 시간에 해결합니다. 라그랑주의 네 제곱수 정리에 따르면 정답은 최대 4이므로 O(sqrt(n))의 수학적 접근도 가능하지만, 예상되는 해법은 DP입니다.
import math
def num_squares(n):
# Generate all perfect squares up to n
squares = [i*i for i in range(1, int(math.sqrt(n)) + 1)]
# Coin change with squares as 'coins'
dp = [float('inf')] * (n + 1)
dp[0] = 0
for i in range(1, n + 1):
for sq in squares:
if i >= sq:
dp[i] = min(dp[i], 1 + dp[i - sq])
return dp[n]
print(num_squares(12)) # 3: 4+4+4
print(num_squares(13)) # 2: 4+9
print(num_squares(1)) # 1: 1DP 디버깅: 흔한 실수
흔한 DP 오류는 다음과 같습니다. 기본 경우를 잘못 설정하는 것(dp[0]을 잘못 설정함), 표를 잘못 채우는 순서(아직 계산하지 않은 값에 접근함), 상태 정의에서 1만큼 어긋나는 오류(i에 도달하는 비용(TO)과 i에서 떠나는 비용(LEAVE)을 혼동함), 그리고 무한대가 남아 있을 때 -1을 반환하지 않는 것(불가능한 경우)입니다. 더 큰 입력을 테스트하기 전에 항상 가장 단순한 경우(빈 입력, 원소 하나, 목표 금액이 0인 경우)를 테스트하세요.
# Common DP debugging checklist:
# 1. Base case: what is dp[0]? dp[1]? Are they correct?
# 2. State definition: write it in English before coding
# 3. Recurrence: trace manually on a 3-element example
# 4. Fill order: dependency arrows point left/up? Fill left/up first
# 5. Infinity check: return -1 or 0 when dp[target] == inf?
# 6. Array bounds: dp has size n+1 for 0..n, or n for 0..n-1?
# Quick test template:
def test_coin_change():
assert coin_change([1], 0) == 0 # base case
assert coin_change([1], 1) == 1 # single coin
assert coin_change([2], 3) == -1 # impossible
assert coin_change([1,5,6,9], 11) == 2
print('All tests passed!')
test_coin_change()빠른 확인
이 단원에서 다룬 자료 구조 및 알고리즘 — 코딩 면접 준비 개념에 대한 이해도를 확인해 보세요.
단원 복습
이 단원에서 배운 내용: 동전 교환 최소 개수 DP(무제한 배낭 문제)와 탐욕적 방법이 실패하는 이유, 동전 반복문을 바깥쪽에 두고 금액 반복문을 안쪽에 두어 조합을 세는 동전 교환 II, 그리고 왼쪽에서 오른쪽 및 오른쪽에서 왼쪽으로 구성하는 두 가지 방법을 사용하는 최소 비용 계단 오르기입니다. 다음으로 집 도둑질, 카데인 알고리즘, 단어 분할을 통해 1차원 DP 패턴을 살펴보겠습니다.
자주 묻는 질문
“동전 교환과 최소 비용 계단” 강의는 무료인가요?
네 — “동전 교환과 최소 비용 계단” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 DSA Interview Prep 강의 전체를 잠금 해제할 수 있습니다. DSA Interview Prep 강의에는 총 4개의 강의가 포함되어 있습니다.
“동전 교환과 최소 비용 계단”에서 뭘 배우나요?
coin-change와 min-cost-climbing-stairs의 점화식을 세우고 올바른 DP 방향을 선택한 뒤 표를 직접 따라가 봅니다. 브라우저에서 직접 실행하는 실습 코드로 DSA Interview Prep을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
DSA Interview Prep을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 DSA Interview Prep은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.
“동전 교환과 최소 비용 계단” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 DSA Interview Prep 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 DSA Interview Prep 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- DP 알아보기: 중복되는 하위 문제
- 메모이제이션을 이용한 하향식 DP
- 표 작성을 이용한 상향식 DP
- 동전 교환과 최소 비용 계단