6. 다이나믹 프로그래밍
💡 동빈나 님의 이코테 2021 강의 몰아보기 를 보면서 공부한 내용을 정리하고 있습니다. 더 자세한 내용은 이것이 취업을 위한 코딩 테스트다 with 파이썬 을 참고해주세요 😊 학습 도구로는 리플렛 을 사용하고 있고 원본 소스코드는 동빈님의 Github 에서 확인할 수 있고 스스로 공부한 소스코드는 Github 에서 확인할 수 있습니다.
다이나믹 프로그래밍
메모리를 적절히 사용하여 수행시간 효율성을 비약적으로 향상시키는 방법
이미 계산된 결과는 별도의 메모리영역에 저장하여 다시 계산하지 않도록 한다.
한번 계산해서 해결한 문제는 다시 계산하지 않도록 한다.
다이나믹 프로그래밍의 구현은 일반적으로 두가지 방식으로 구성된다.
top-down
bottom-up
동적 계획법이라고도 부름!
일반적으로 동적이라는 의미 in programming
자료구조에서 동적할당은 프로그램이 실행되는 도중에 실행에 필요한 메모리를 할당하는 기법
다이내믹 프로그래밍에서 다이내믹은 별다른 의미 없이 사용된 것임
다음의 두 경우에 쓴다.
최적 부분 구조
optimal substructure
큰 문제를 작은 문제로 나눌 수 있으며 작은 문제의 답을 모아서 큰 문제를 해결할 수 있다.
중복되는 부분 문제
overlapping subproblem
동일한 작은 문제를 반복적으로 해결해야한다.
예
피보나치수열
피보나치 수열
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
점화식
인접한 항들 사이의 관계식을 의미
피보나치 수열을 점화식으로 표현
프로그램 상에서는 수열ㅇ르 배열이나 리스트를 이용해서 표현한다.
하지만 이렇게 단순이 재귀함수로 호출할 경우 지수 시간 복잡도를 가지게 됨
N의 값이 조금만 커지더라도 문제가 됨
빅오표기법 : O(2^n)
단순히 6번째 피보나치 수만 구하더라도 중복이 5번이나 발생하게 된다.
한번 연산된 결과는 메모리에 보관할 수 있도록 한다.
f(30) 을 구하기 위해서는 10억 가량의 연산을 수행해야함
f(100) 을 구하기 위해서는? 비현실적인 수행시간이 발생하게 된다.
해결방법은? 다이나믹 프로그래밍!
다음의 조건을 만족하나?
최적 부분 구조 : 큰 문제를 작은 문제로 나눌 수 있다. -> 만족! f(4) 를 구하기 위해서 f(3), f(2) 를 구해야함
중복되는 부분 문제 : 동일한 작은 문제를 반복적으로 해결한다. -> 여러번 중복 연산을 하게 됨
만족한다고 판단됨!
메모이제이션
한번 계산한 결과를 메모리 공간에 메모하는 기법
같은 문제를 다시 호출하면 메모했던 결과를 그대로 가져온다.
값을 기록해놓는다는 점에서 캐싱이라고 한다.
그래서 변수도 caching, d, dp, table 등을 사용한다.
메모이제이션(탑다운)은 하향식 방법
보텀업 방식은 상향식! -> 반복문을 이용함
다이나믹 프로그래밍의 전형적인 형태는 보텀업 방식이다.
결과 저장용 리스트는 dp table 이라고 부른다.
엄밀히 말하자면 메모이제이션은 이전에 계산된 결과를 일시적으로 기록해놓는 넓은 개념을 의미한다.
다이나믹 프로그래밍에 국하된 개념은 아니다.
한번 계산된 결과를 담아놓기만 하고 다이나믹 프로그래밍을 위해 활용하지 않을 수도 있다.
따라서 메모이제이션 != 다이나믹 프로그래밍
상향식 메모이제이션으로 피보나치 수열 해결해보기
별도의 리스트를 사용하여 문제를 풀면 답을 계속 기록해나간다는 점에서, 해당 문제가 이전에 해결된적 있는 문제인지 알 수 있다.
하향식 메모이제이션으로 피보나치 수열 해결해보기
메모이제이션을 이용하는 경우 시간 복잡도는 O(N) 이 된다.
메모리를 N만큼 가질 수 있다는 전제하에!
다이나믹 프로그래밍 vs 분할정복
공통점 : 모두 최적 부분 구조를 가질 때 사용할 수 있다.
큰 문제를 작은 문제로 나누고
작은 문제의 답을 모아서 큰 문제를 해결한다.
차이점 : 부분문제의 중복
다이나믹 프로그래밍 : 각 부분 ㅁ누제들이 서로 영향을 미치고 부분 문제가 중복됨
분할 정복 문제 : 동일한 부분문제가 반복적으로 계산되지 않는다.
다이나믹 프로그밍 접근 방법
가장 먼저 그리디, 구현, 완전탐색 아이디어로 문제 해결할 수 있는지 검토
다른 알고리즘으로 풀이방법이 떠오르지 않으면 다이나믹 프로그래밍을 고려한다.
재귀함수로 비효율적인 완전 탐색 프로그램을 작성한 뒤 (탑다운) 작은 문제에서 구한 답이 큰 문제에서 그대로 사용될 수 있다면 코드를 개선하는 방법을 사용한다.
예) 피보나치 수열
일반적인 코테에서는 기본 유형의 다이내믹 프로그래밍 문제가 출제되는 경우가 많다.
개미전사 문제
개미전사는 부족한 식량을 보충하고자 메뚜기 마을의 식량창고를 공격하려고 한다.
메뚜기 마을의 식량창고는 일직선으로 이어져있다.
각 창고는 정해진 수의 식량들이 저장되어있고 개미 전사들은 선택적으로 약탈하여 식량을 최대한 많이 빼앗을 예정이다.
일직선상에 존재하는 식량창고 중에서 서로 인접한 식량창고가 공격받으면 바로 알아챌 수 있기에 최소 한칸이상 떨어진 식량창고를 약탈해야한다.
예시
<창고 0 : 1개> - <창고 1 : 3개> - <창고 2 : 1개> - <창고 3 : 5개>
이때는 두번째와 네번째 창고를 털어야 최대 식량값인 8개를 빼앗을 수 있다.
식량창고 N의 정보가 주어졌을 때 얻을 수 있는 식량의 최댓값을 구하라!
조건
식량창고 N의 개수가 3이상 100이하로 주어진다.
각 창고에 저장된 식량 개수 K 가 공백 기준으로 주어진다.
해결 아이디어
N=4 일 때, 총 8가지의 경우의 수가 있음
이 때 가장 많은 식량을 약탈하는 경우의 수는 3개와 8개를 약탈하는 8개의 경우이다.
ai = i번째 식량창고까지의 최적의 해 라고 가정한다면 다이나믹 프로그래밍을 적용할 수 있다.
DP table
a0 = 1
a1 = 3
a2 = 3
a3 = 3+5 = 8
왼쪽부터 식량창고를 턴다고 했을 때, 특정 i번째 식량창고에 대해서 털지 안털지 여부를 결정하면 2가지 중에서 더 많은 식량을 털 수 있는 경우를 선택하면 된다.
1로 만들기 문제
문제
정수 x가 주어졌을 때 정수 x에 사용할 수 있는 연산은 다음과 같이 4가지 이다.
x가 5로 나누어지면 5로 나눈다.
x가 3으로 나누어지면 3으로 나눈다.
x가 2로 나누어지면 2로 나눈다.
x -1을 한다.
이 때, 연산 4개를 이용해서 값을 1로 만들고자 할 때, 연산의 최소값을 구하는 문제
예시
26 -> 25 -> 5 -> 1
조건
x는 1이상 3만 이하로 주어진다.
연산하는 횟수의 최솟값을 출력해야한다.
앞선 그리디 해법으로 풀 수 없는 이유
단순히 나누기가 가능할때 나누는 것보다 뺄샘과 나눗셈을 적절하게 사용해서 푸는 것이 훨씬 더 이득이 된다.
예를들면, 26의 경우,
2로 나누어진다고 해서 2로 나눗셈을 하는 것보다 -> 6번
-1을 먼저하고 5로 두 번 나누는 것이 연산 횟수를 더 최소화할 수 있다. -> 3번
오히려 이 문제는 최적 부분 구조와 중복 되는 부분 문제를 만족하므로 다이나믹 프로그래밍을 적용하는 것이 해법이 될 수 있다.
효율적인 화폐구성 문제
문제
N가지 종류의 화폐가 있다.
화폐 개수를 최소한으로 해서 그 가치의 합이 M원이 되도록 하려고 한다.
각 종류의 화폐는 몇 개라도 사용할 수 있다.
예를 들어서 2원, 3원 단위의 화폐가 있을 때에는 15원을 만들기 위해서 3원 * 5개를 사용하는 것이 가장 최소한의 화폐 개수이다.
M원을 만들기 위한 최소한의 화폐 개수를 출력하세요
조건
N, M이 주어진다.
1<= N <= 100, 1<= M <= 10,000
이후의 줄에는 N개 만큼 각 화폐의 가치가 주어진다. 화폐의 가치는 10,000보다 작거나 같은 자연수임
불가능 할 때에는 -1을 출력한다.
예시 1)
입력
2 15
2
3
출력
5
예시 2)
입력
3 4
3
5
7
출력
-1
해결 아이디어
초기화
제일 처음 값인 0만 0으로 초기화해주고
나머지 값들은 모두 불가능하다는 의미로 현재 주어진 수 범위에서 나올 수 없는 값을 임의로 넣어준다.
이 문제에서는 10,001이 될 수 있음 (1<= M <= 10,000)
첫번째 화폐 단위인 2를 살펴본다.
각 화폐단위의 배수값은 모두 만들 수 있기 때문에 최소화폐금액 + 1 씩 하여 개수를 초기화해준다.
이 턴에서는 2, 4, 6, ... 등 2의 배수 금액들은 모두 개수가 업데이트 된다.
두번째 화폐 단위인 3을 점검한다.
3을 통해서 만들 수 있는 화폐 금액인 3, 6, ... 등을 업데이트 한다.
이 턴에서는 7도 초기화 될 수 있는데 7 의 세칸 앞인 4에서 3을 더하면 7을 만들 수 있기 때문에 3으로 업데이트 할 수 있다. (7 = 2+2+3)
세번째 화폐 단위인 5를 점검한다.
5를 통해서 만들 수 있는 금액을 업데이트 하고
5를 통해서 개수를 줄일 수 있는 수들도 업데이트 한다.
결론적으로 7을 만들기 위한 최소한의 화폐 개수는 2개가 된다!
금광 문제
조건
입력
출력
해결 아이디어
Last updated