Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3-rivkms #11

Merged
merged 2 commits into from
Feb 21, 2024
Merged

3-rivkms #11

merged 2 commits into from
Feb 21, 2024

Conversation

rivkms
Copy link
Collaborator

@rivkms rivkms commented Feb 18, 2024

🔗 문제 링크

쉬운 계단 수

디코에서 풀어보면 좋을 것이라고 공유해주셔서 한번 풀어봤습니다.
재밌는 문제였어요 😁

✔️ 소요된 시간

1~2h

✨ 수도 코드

▶️ 문제 유형

: DP : 큰 문제를 작은 문제로 나누어서 풀이하는 방식의 알고리즘. [동적계획법, 메모이제이션]
1차시에 이어 다시 DP 문제로 돌아왔습니다.
하지만 다른 풀이 방식을 사용하여 DP 풀이의 새로운 방법을 제시할 것이니 기대하셔도 좋을 것 같습니다.

❓왜 DP인가(문제 크기를 어떻게 줄이지?)

앞자리에 x(0~9사이의 임의의 수)가 들어가는 길이 n의 계단 수의 개수를 구하는 방법
앞자리에 x-1x+1이 들어가는 길이 n-1의 계단 수개수의 합

‼️아하 n번째를 구하기 위해서 n-1번째의 값을 알면 쉽게 풀 수 있겠구나

▶️ 그림으로 봅시다

‼️ 재귀로 문제를 풀어볼 수 있을 것 같다.

∴ Top-down 방식이라고 함

❓점화식

num[n][a] = num[n-1][a-1] + num[n-1][a+1]

▶️ n : 계단수의 길이, a : 계단수가 시작하는 값

❓탈출조건 :

길이가 1이면서 a로 시작하는 계단 수는 1개밖에 없음

한 자릿수는 각 a에 대하여 하나씩 존재

if(n==1)
    num[n][a] = 1 //이 조건은 초기조건으로 세팅하여 코드상에는 없음

❓추가적인 고려사항

1️⃣ n번째 자리가 0일 때

num[n][a] = num[n-1][1]

2️⃣ n번째 자리가 9일 때

num[n][a] = num[n-1][8]

🛑 BUT

근데 재귀를 사용하면 오버헤드가 발생할 수 있죠?

위 그림에서 빨간색은 중복되서 계산되게 되는데, 같은 계산을 여러 번 해야해서 시간이 낭비됩니다.

‼️ 추가 탈출조건

1️⃣ 계산된 값을 배열에 저장
2️⃣ 배열에 미리 계산된 값이 있으면 그 결과를 가져옴

이 방법으로 불필요한 계산을 줄일 수 있는데 이 방법을 메모이제이션 기법이라 합니다.

메모이제이션

값비싼 함수 호출의 결과를 캐싱하고 동일한 입력이 다시 발생할 때 불필요하게 다시 계산하는 대신 캐싱된 결과를 반환하는 프로그래밍 기술. 성능 하면 빠질 수 없는 메모이제이션, 네가 궁금해

자 그렇다면 위의 내용을 종합하여 Sudo code를 작성해보면

1️⃣  계단 수의 자릿수인 n을 입력받는다. 
2️⃣ 메모이제이션 계산 결과를 저장할 벡터 vec의 초기값을 설정한다. 
3️⃣ 앞자리 수가 1~9인 경우에 대하여 함수를 호출한다.
---함수---
1️⃣ vec에 값이 있으면 그 값을 반환한다. 
2️⃣ a가 0이라면 0으로 시작하는 n-1 길이의 계단 수의 개수를 반환한다. 
3️⃣ a가 9이라면 8로 시작하는 n-1 길이의 계단 수의 개수를 반환한다. 
4️⃣ 그 외의 경우 각 앞자리가 a-1, a+1로 시작하는 n-1 길이의 계단 수의 개수의 합을 반환한다. 
5️⃣ 위의 모든 경우에 대하여 메모이제이션에 결과를 저장한다. 
---끝---
4️⃣ 함수의 결과를 sum에 저장한다. 
5️⃣ sum을 출력한다. 

🖥️ 코드 구현

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

vector<vector<int>> vec(101,vector<int> (10,0));

int func(const int & n, const int & a){
    if(vec[n][a]){
        return vec[n][a];
    }
    else if(a==0){
        return vec[n][a] = func(n-1, 1)%1000000000;
    }
    else if(a==9){
        return vec[n][a] = func(n-1, 8)%1000000000;
    }
    return vec[n][a] = ((func(n-1, a-1)) + (func(n-1, a+1)))%1000000000;
}

int main(){
    int n, sum = 0;
    cin >> n;
    
    
    for(int i = 0 ;i<=9; i++){
        vec[1][i] = 1;
    }
    
    for(int i = 1; i<=9; i++){
        sum+=func(n ,i);
        sum%=1000000000;
    }
    cout << sum;
    return 0;
}

> 문제 상에서 1000000000로 나눈 나머지를 출력하라 했기에 그 과정도 포함되어 있습니다. 

📚 새롭게 알게된 내용

✔️ 1차시 때와 이번 차시에 DP문제를 다루어봤는데 서로 다른 풀이 방법을 사용해보았습니다.
이번 차이에는 Top-bottom 방식을, 1차시 때에는 Bottom-up 방식을 사용해보았습니다.

Top-bottom 차이 Bottom-up
큰 문제를 해결하기 위해 작은 문제를 호출 Start 작은 문제부터 차근차근 답을 도출해나감
재귀 함수 Start 반복문
메모이제이션 Center DP 테이블

이러한 주요한 차이점을 이해하시고 두 방법 모두 프로그래밍 하실 수 있으시면 DP에 대해 어느 정도 이해한 것이 아닐까요?
관련하여 링크 몇 개 달아두겠습니다.

1️⃣ 동적 계획법( DP - Dynamic Programming ), 메모이제이션( Memoization )

2️⃣ [Algorithm] Top-Down, Bottom-Up 이란?

3️⃣ 20. 다이나믹 프로그래밍(Dynamic Programming)

✔️ 메모이제이션 기법과 DP의 차이점, 그리고 Top-down방식과 Bottom-up 방식의 명확한 차이와 그 관계에 대하여 명확한 설명을 못할 것 같았는데, 이번 기회에 확실하게 알 수 있었던 것 같습니다.

@rivkms rivkms changed the title 2024-02-18 쉬운 계단 수 3-rivkms Feb 18, 2024
Copy link
Collaborator

@YIM2UL2ET YIM2UL2ET left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DP를 잘 몰라도 DP테이블과, 자료까지 첨부해주시니 어느 정도 이해가 되네요. 문제 시각화부터 예외 처리까지 배울 점이 많은 코드였습니다. 지난 차시에 이어서 재귀에 관한 팁도 잘 얻어갑니다. 나중에 DP 공부할 때 첨부해주신 자료도 다시 한번 읽어봐야겠네요. 수고하셨습니다.

Copy link
Member

@kjs254 kjs254 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

n 번째 값을 이용하기 위해 n-1 번째 값을 구하는 재귀 방식을 아주 잘 이해할 수 있었습니다.

저번에 올라왔던 하노이탑 문제와도 비슷한 계열인 것 같습니다.

이번에 사용한 재귀 방식이 DP 방식의 한 종류로 이해하게 되었습니다. 감사합니다.



for(int i = 0 ;i<=9; i++){
vec[1][i] = 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

초기조건을 설정한 부분인데 vec[n][a] 을 전역변수로 처리하는 과정이 혹시 어디에 있는지도 물어봐도 괜찮을까요?

제가 c++이 익숙하지 않아 질문드립니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

함수 외부에

vector<vector<int>> vec(101,vector<int> (10,0));

와 같은 코드가 있는 것을 볼 수 있으실 겁니다.

이게 다른 언어로 치면 2차원 배열을 선언한 것인데, 특정 함수 내에 들어가있지 않고 외부에서 전역변수로 선언하셨다고 생각하시면 될 것 같습니다.

Comment on lines +10 to +11
if(vec[n][a]){
return vec[n][a];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위 부분이 메모제이션 방식을 구현한 코드가 맞는지 궁금합니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메모이제이션이라고 보셔도 된다고 생각합니다.
해당 배열에 값이 있다면(한번 계산이 되었다면) 그 값을 불러와 사용하는 것이니 메모이제이션 기법을 사용하였다고 보셔도 될 것 같습니다.

물론 저 코드만을 메모이제이션으로 생각하시기 보단,
"함수의 계산 값을 저장하여 같은 계산을 줄이고자 하는 과정" 자체를 메모이제이션으로 받아들이셔서
해당 부분 외에도 함수의 결과를 배열에 저장하는 과정도 메모이제이션 방식을 사용하셨다고 생각하시면 좋을 것 같습니다.

@rivkms rivkms merged commit eb633b5 into main Feb 21, 2024
@rivkms rivkms deleted the 3-rivkms branch February 21, 2024 14:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants