컴퓨터 공부/📚 프로그래머스

[프로그래머스] 코딩테스트 연습 - 거스름돈(Python)

letzgorats 2022. 12. 11. 18:49

문제 설명

더보기

문제

Finn은 편의점에서 야간 아르바이트를 하고 있습니다. 야간에 손님이 너무 없어 심심한 Finn은 손님들께 거스름돈을 n 원을 줄 때 방법의 경우의 수를 구하기로 하였습니다.

예를 들어서 손님께 5원을 거슬러 줘야 하고 1원, 2원, 5원이 있다면 다음과 같이 4가지 방법으로 5원을 거슬러 줄 수 있습니다.

  • 1원을 5개 사용해서 거슬러 준다.
  • 1원을 3개 사용하고, 2원을 1개 사용해서 거슬러 준다.
  • 1원을 1개 사용하고, 2원을 2개 사용해서 거슬러 준다.
  • 5원을 1개 사용해서 거슬러 준다.

거슬러 줘야 하는 금액 n과 Finn이 현재 보유하고 있는 돈의 종류 money가 매개변수로 주어질 때, Finn이 n 원을 거슬러 줄 방법의 수를 return 하도록 solution 함수를 완성해 주세요.

제한

  • n은 100,000 이하의 자연수입니다.
  • 화폐 단위는 100종류 이하입니다.
  • 모든 화폐는 무한하게 있다고 가정합니다.
  • 정답이 커질 수 있으니, 1,000,000,007로 나눈 나머지를 return 해주세요.

입출력 예시

입출력 예

n money result
5 [1,2,5] 4

입출력 예 설명

입출력 예 #1
문제의 예시와 같습니다.

https://school.programmers.co.kr/learn/courses/30/lessons/12907

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

Level3 문제입니다. 유명한 DP 문제 중 하나입니다. 거스름돈 문제와 관련한 DP문제만 해도 유형이 몇 개 있는데요. 그 중 가장 기본적인 DP문제라고 보시면 됩니다.

 

DP알고리즘을 사용해야 하는 이유는 계산하고자 하는 답을 구하기 위해서는 메모이제이션을 통해 누적하여 합산하는 방식으로 풀어야 하기 때문입니다. 다시 말해, 답으로 향하는 값에 이전값이 영향을 크게 주기 때문이죠.

DP는 제가 제일 약한 파트이기도 한 것 같습니다. 점화식을 생각하고 구현하는 것이 쉽게 머리에 떠오르지 않아서 그런 것 같은데요, 그래서 DP알고리즘은 코드는 짧지만 난이도가 있는 편에 속한 문제들이 종종 있습니다.

 

해당 풀이는 코드를 토대로 설명해보겠습니다.

def solution(n, money):
    
    dp = [1] + [0] * (n)
    
    for m in money:
        for i in range(m,n+1):
            dp[i] += dp[i - m]
            
    return dp[n % 1000000007]

우선 dp배열을 초기화하는 것이 필요합니다. 거스름돈 n원을 주는 방법을 구하는 것이 최종 목적지이므로 dp[n]이 답이 되도록 dp배열을 n+1 길이만큼 초기화 해줍니다.

 

이제 본격적으로, 점화식을 생각해야 할 차례입니다. 현재 보유하고 있는 돈의 종류가 담긴 money배열을 외부 반복문으로 설정합니다. 아무래도 직관적으로 이해하기 편하니까요.

money를 돌면서 현재 순회하고 있는 동전으로 n원 이하까지의 금액을 만들 수 있는 경우의 수를 dp리스트에 갱신해줍니다. 이 때, 만들어야 하는 금액은 현재 선택한 동전보다 작은 경우는 어차피 못 만드니까 고려하지 않아도 됩니다.

때문에, 내부 반복문에서 i의 범위는 range(m, n + 1)로 설정합니다.

 

이 로직은 다시 말해 money에 담겨 있는 동전으로 1부터 N원까지 만들 수 있는 경우의 수를 누적하고 또 다시 다음 동전으로 1원부터 N원까지 만들 수 있는 경우의 수를 누적하면서 최종 N원을 만들 수 있는 모든 경우의 수를 출력하는 로직입니다.

 

예시를 들어봅시다.

money에 1원, 2원, 5원 이 있다고 하면, 

1원으로 1원부터 N원까지 만들 수 있는 방법을 dp배열에 최적화하고

2원으로 2원부터 N원까지 만들 수 있는 방법을 dp배열에 갱신하고

5원으로 5원부터 N원까지 만들 수 있는 방법을 dp배열에 또 추가하는 로직입니다.

 

이 때, 갱신하는 점화식으로는

for m in money:
        for i in range(m,n+1):
            dp[i] += dp[i - m]

dp[거스름돈 금액] = dp[거스름돈 금액] + dp[거스름돈 금액 - 사용할 동전의 금액] 식을 만들어 

dp[거스름돈 금액] += dp[거스름돈 금액 - 사용할 동전의 금액]으로 표현할 수 있습니다.

이는 결과물에 지금 선택되어있는 화폐의 가치를 더함을 의미합니다.

 

이게 대체 무슨 말이냐! 하면 다시 생각해봅시다.

 

예를 들어 2원을 만드려고 하면 1원을 만들 때 사용된 경우의 수에 지금 선택된 화폐의 가치만 더해주면 되기 때문에 이를 반복하면 원하는 결과까지 갈 수 있는 것입니다.

 

다음 표를 살펴봐서 이해를 확실히 해봅시다.

거스름돈 1 2 3 4 5 6 7 8 9
1원 동전 1 1 1 1 1 1 1 1 1
2원 동전 1 1+1 1+1 1+2 1+2 1+3 1+3 1+4 1+4
5원 동전 1 1+1 1+1 1+2 1+2+1 1+3+1 1+3+2 1+4+2 1+4+3
경우의 수 1 2 2 3 4 5 6 7 8

1원 동전으로 1~9원까지의 거스름돈을 주는 경우의 수는 모두 1 입니다.

2원 동전까지 포함해서 1~9원까지의 거스름돈을 주는 경우의 수는 n-2원을 표현하는 경우의 수를 더해주면 됩니다.

 

즉, 거슬러 줘야 하는 금액이 3원의 경우 기존에 1원 동전만 사용해서 3원을 만드는 경우의 수 1

2원 동전을 사용하는 경우의 수 1

을 더한 2는 1원 동전과 2원 동전으로 3원을 만들 수 있는 경우의 수입니다.

 

그렇다면 1원과 2원짜리 동전을 사용해서 9원을 만드는 경우의 수를 구해보면,

1원짜리 동전만을 사용한 경우의 수 1 과

1원짜리 동전과 2원짜리 동전으로 7원(9-2)을 만드는 경우의 수 4를 더한 5인 셈입니다.

 

마지막으로, 1원과 2원과 5원짜리 동전으로 9원을 만드는 경우의 수를 생각해본다면,

1원짜리 동전만을 사용한 경우의 수 1 과

1원짜리 동전과 2원짜리 동전으로 7원을 만드는 경우의 수를 더한 4 와

1원짜리 동전과 2원짜리 동전과 5원짜리 동전으로 4원을 만드는 경우의 수 3 을 더한 8인 셈입니다.

(*여기서 4원은 5원이 있어도 1원짜리 동전과 2원짜리 동전으로만 만들 수 있는 3가지 경우겠죠?)

 

이런식으로 작은 금액의 동전부터 차례대로 동전의 종류를 늘려가면서 n원을 표현할 수 있는 경우의 수를 카운팅하면 됩니다. 즉, 모든 동전에 대한 값을 누적하여 위의 결과와 같이 거스름돈 N원을 만드는 총 경우의 수는 dp[n]에 저장되어 있을 것입니다.

 

 

반응형