비용이 있는 계단 오르기를 메모이제이션으로

paul 2025.04.20 336words (1m)

계단마다 비용이 있고 한 칸 또는 두 칸씩 올라 꼭대기에 도달하는 최소 비용을 구하는 문제다. 밟은 칸의 비용을 낸다.

python
stairs = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]

100이 박힌 자리를 피해 가는 게 관건이다.

경로를 다 만들어보기

python
def rec(cur, sum=0):
    if len(cur) <= 1:
        store.append(sum)
        return

    rec(cur[1:], sum + cur[0])
    rec(cur[2:], sum + cur[1])

store = []
rec(stairs)
print(min(store))

리스트를 잘라가며 내려간다. 한 칸 오르면 cur[0] 을 내고 cur[1:] 이 남고, 두 칸이면 cur[1] 을 내고 cur[2:] 가 남는다.

답은 6이고 만들어진 경로는 89개다. 계단 10개에 89개니 계단이 늘어나면 피보나치 수만큼 불어난다.

남은 길이만 상태로

cur 를 통째로 들고 다니는데, 실제로 다른 건 남은 길이 하나다. 길이가 같으면 앞으로 낼 비용도 같다.

python
def dyna(cur_len):
    if cur_len in dp:
        return dp[cur_len]

    dp[cur_len] = min(dyna(cur_len - 1), dyna(cur_len - 2)) + stairs[cur_len]
    return dp[cur_len]

length = len(stairs)
stairs.append(0)
dp = {-2:0, -1:0}

dyna(length)
print(dp[length])

리스트를 자르지도 않는다. 인덱스 하나만 넘긴다.

경계를 없앤 두 가지 장치

이 코드에서 가장 마음에 드는 부분이다. 보통 if cur_len < 0: return 0 같은 걸 넣는데 여기엔 없다.

python
stairs.append(0)

비용 0짜리 계단을 끝에 하나 붙였다. 꼭대기가 실제 계단이 되므로 "다 올라왔다"를 따로 처리할 필요가 없다. dyna(length) 가 그 가상의 계단이다.

python
dp = {-2:0, -1:0}

음수 인덱스를 미리 채워뒀다. dyna(0)dyna(-1)dyna(-2) 를 부르는데, 둘 다 dp 에 이미 있어서 바로 0을 돌려준다. 재귀가 거기서 멈춘다.

두 개 다 종료 조건을 코드가 아니라 데이터로 옮긴 것이다. if 가 하나도 없이 세 줄로 끝난다.

아래에서 위로도 써봤다

python
dp = {-2:0, -1:0}

for i in range(length):
    dp[i] = min(dp[i - 1], dp[i - 2]) + stairs[i]

같은 점화식을 반복문으로 돌린 것이다. dict를 그대로 써서 dp[-1]dp[-2] 가 초기값으로 잡히는 것도 같다.

다만 range(length)i 가 9까지만 간다. 끝에 붙여둔 비용 0짜리 계단인 dp[10] 은 계산되지 않는다. 이 입력에서는 dp[9] 가 6이라 답이 맞아 보이는데, 마지막 계단이 비쌌다면 그 칸을 건너뛰는 선택을 못 하게 된다. range(length + 1) 이어야 위쪽 재귀 버전과 같아진다.