Algorithm

[μ•Œκ³ λ¦¬μ¦˜] 동적 κ³„νšλ²•(Dynamic Programming, DP) μ•Œμ•„λ³΄κΈ° ( λ°±μ€€ 24416번 ν”Όλ³΄λ‚˜μΉ˜ 수-1 with μžλ°” )

μž₯μš©μ„ 2024. 3. 15. 01:14

 

 

πŸ€” 동적 κ³„νšλ²• DP?

λ³΅μž‘ν•œ 문제λ₯Ό 더 μž‘μ€ ν•˜μœ„ 문제둜 λ‚˜λˆ„μ–΄ ν•΄κ²°ν•˜λŠ” μ•Œκ³ λ¦¬μ¦˜ 섀계 κΈ°λ²•μž…λ‹ˆλ‹€.

즉, 문제 해결을 μœ„ν•΄ μ„€κ³„ν•˜λŠ” λ°©λ²•μ΄λ‚˜ μ ‘κ·Ό 방식을 λ§ν•©λ‹ˆλ‹€.

 

πŸ’‘ λ©”λͺ¨μ΄μ œμ΄μ…˜(Memoization)

λ™μΌν•œ 계산을 λ°˜λ³΅ν•΄μ•Ό ν•  λ•Œ, 이전에 κ³„μ‚°ν•œ 값을 λ©”λͺ¨λ¦¬μ— μ €μž₯ν•¨μœΌλ‘œμ¨ λ™μΌν•œ κ³„μ‚°μ˜ 반볡 μˆ˜ν–‰μ„ μ œκ±°ν•˜μ—¬ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 속도λ₯Ό λΉ λ₯΄κ²Œ ν•˜λŠ” κΈ°μˆ μ΄λ‹€. 동적 κ³„νšλ²•μ˜ 핡심이 λ˜λŠ” κΈ°μˆ μ΄λ‹€.

wikipedia

동적 κ³„νšλ²•(DP)의 핡심이 λ˜λŠ” 기술둜 κ°€μž₯ 큰 νŠΉμ§•μ΄λΌκ³  ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

상ν–₯식, ν•˜ν–₯식 μ ‘κ·Ό 방식을 톡해 문제λ₯Ό μž‘μ€ ν•˜μœ„ 문제둜 λ‚˜λˆ„μ–΄ 이λ₯Ό ν•΄κ²°ν•˜λ©΄μ„œ ν•˜μœ„ 문제의 κ²°κ³Όλ₯Ό λ©”λͺ¨μ΄μ œμ΄μ…˜ 즉 λ©”λͺ¨λ¦¬μ— μ €μž₯ν•¨μœΌλ‘œμ¨ 쀑볡 연산을 쀄일 수 μžˆμŠ΅λ‹ˆλ‹€.

 

πŸ”Ž 상ν–₯식(bottom-up) μ ‘κ·Ό

μž‘μ€ ν•˜μœ„ λ¬Έμ œλ“€λΆ€ν„° μ‹œμž‘ν•˜μ—¬ κ·Έ κ²°κ³Όλ₯Ό μ €μž₯ν•˜κ³ , 이λ₯Ό μ΄μš©ν•˜μ—¬ μ μ§„μ μœΌλ‘œ 큰 문제의 닡을 κ΅¬ν•΄λ‚˜κ°€λŠ” μ ‘κ·Ό λ°©μ‹μž…λ‹ˆλ‹€.

 

πŸ”Ž ν•˜ν–₯식(top-down) μ ‘κ·Ό

μž¬κ·€ ν˜ΈμΆœμ„ μ΄μš©ν•˜μ—¬ 문제λ₯Ό ν•΄κ²°ν•  λ•Œ 주둜 μ“°μž…λ‹ˆλ‹€.

즉, 큰 문제λ₯Ό μž‘μ€ ν•˜μœ„ 문제둜 λ‚˜λˆ„μ–΄ ν•΄κ²°ν•˜λŠ” μ ‘κ·Ό λ°©μ‹μž…λ‹ˆλ‹€.

 

 

βœ” 동적 κ³„νšλ²•(DP) 적용 쑰건

πŸ”Ž μ€‘λ³΅λ˜λŠ” λΆ€λΆ„ 문제 (Overlapping Subproblems)

DPλŠ” 문제λ₯Ό μž‘μ€ ν•˜μœ„ 문제둜 λ‚˜λˆ„κ³ , μž‘μ€ 문제의 κ²°κ³Όλ₯Ό μž¬μ‚¬μš©ν•˜μ—¬ μ›ν•˜λŠ” κ²°κ³Όλ₯Ό λ„μΆœν•˜λŠ” 과정이라고 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λ”°λΌμ„œ μž‘μ€ 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ λ™μΌν•œ μž‘μ€ λΆ€λΆ„ λ¬Έμ œκ°€ 반볡적으둜 λ°œμƒν•˜λŠ” 경우 DPλ₯Ό μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

πŸ”Ž 졜적 λΆ€λΆ„ ꡬ쑰 (Optimal Substructure)

μ£Όμ–΄μ§„ 문제의 졜적의 닡이 ν•˜μœ„ 문제의 졜적의 닡을 톡해 μ–»μ–΄μ§ˆ 수 μžˆλŠ” 경우λ₯Ό λ§ν•©λ‹ˆλ‹€.

 

πŸ‘€ Example

public static int fibonacci(int n ){
    if( n == 1 || n == 2){
        return 1;
    }

    return fibonacci(n-1) + fibonacci(n - 2);

}

 

μœ„ μ½”λ“œλŠ” ν”Όλ³΄λ‚˜μΉ˜ 수λ₯Ό μž¬κ·€ ν˜ΈμΆœμ„ 톡해 κ΅¬ν˜„ν•΄ λ³΄μ•˜μŠ΅λ‹ˆλ‹€.

ν”Όλ³΄λ‚˜μΉ˜ μˆ˜μ—΄μ€ 이전 두 숫자의 합을 λ‹€μŒ 숫자둜 λ‚˜νƒ€λ‚΄λŠ” μˆ˜μ—΄μž…λ‹ˆλ‹€.

λ‹€μŒ 숫자λ₯Ό μ•Œμ•„λ‚΄λ €λ©΄ 이전 μˆ«μžλ“€μ˜ 합을 μ•Œμ•„μ•Ό ν•©λ‹ˆλ‹€ 이 κ³Όμ •μ—μ„œ 그림처럼 μ€‘λ³΅λ˜λŠ” μ—°μ‚° 즉, μ€‘λ³΅λ˜λŠ” λΆ€λΆ„ λ¬Έμ œκ°€ λ°œμƒν•œλ‹€λŠ” κ±Έ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.

λ˜ν•œ 전체 문제(fibonacci(5))의 닡을 λΆ€λΆ„ λ¬Έμ œλ“€μ˜ 닡을 톡해 μ–»μ–΄μ§ˆ 수 있기 λ•Œλ¬Έμ— 졜적 λΆ€λΆ„ ꡬ쑰라고 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

두 쑰건을 λ§Œμ‘±ν•¨μœΌλ‘œ ν”Όλ³΄λ‚˜μΉ˜ 수λ₯Ό κ΅¬ν•˜κΈ° μœ„ν•΄μ„œ DP방법둠을 μ μš©ν•΄ λ³Ό .

 

 

βœ” DP 문제

λ‚œμ΄λ„ : 브둠즈 1 πŸ₯‰

λ°±μ€€ 24416번 ν”Όλ³΄λ‚˜μΉ˜ 수 1

 

24416번: μ•Œκ³ λ¦¬μ¦˜ μˆ˜μ—… - ν”Όλ³΄λ‚˜μΉ˜ 수 1

μ˜€λŠ˜λ„ μ„œμ€€μ΄λŠ” 동적 ν”„λ‘œκ·Έλž˜λ° μˆ˜μ—… 쑰ꡐλ₯Ό ν•˜κ³  μžˆλ‹€. μ•„λΉ κ°€ μˆ˜μ—…ν•œ λ‚΄μš©μ„ 학생듀이 잘 μ΄ν•΄ν–ˆλŠ”μ§€ 문제λ₯Ό ν†΅ν•΄μ„œ ν™•μΈν•΄λ³΄μž. μ˜€λŠ˜μ€ n의 ν”Όλ³΄λ‚˜μΉ˜ 수λ₯Ό μž¬κ·€ν˜ΈμΆœκ³Ό 동적 ν”„λ‘œκ·Έλž˜λ°

www.acmicpc.net

 

n의 ν”Όλ³΄λ‚˜μΉ˜ 수λ₯Ό μž¬κ·€ 호좜과 DPλ₯Ό 톡해 κ΅¬ν•˜λŠ” μ•Œκ³ λ¦¬μ¦˜μ„ λΉ„κ΅ν•˜κΈ° μœ„ν•΄ 각 μ½”λ“œκ°€ λͺ‡ 번 ν˜ΈμΆœλ˜λŠ”μ§€ 호좜 횟수λ₯Ό 좜λ ₯ν•˜λŠ” λ¬Έμ œμž…λ‹ˆλ‹€.

 

DP방법둠을 μ μš©ν•œ μ½”λ“œμž…λ‹ˆλ‹€.

bottom-up 접근을 톡해 μž‘μ€ 문제 즉, ν”Όλ³΄λ‚˜μΉ˜ 수의 첫번째 수, 두 번째 수 연산을 μ‹œμž‘μœΌλ‘œ 결괏값을 μ €μž₯ν•˜λ©΄μ„œ μ €μž₯ν•œ 결괏값을 톡해 쀑볡 연산을 쀄이고 점차 n번째 μˆ˜κΉŒμ§€ 값을 λ„μΆœν•΄ λ‚΄λŠ” κ³Όμ •μœΌλ‘œ μ§„ν–‰ν•©λ‹ˆλ‹€.

DP의 핡심인 λ©”λͺ¨μ΄μ œμ΄μ…˜μ„ ν†΅ν•΄μ„œ 이전 값듀을 μ €μž₯ν•˜μ—¬ 쀑볡 연산을 μ œκ±°ν•˜κΈ° μœ„ν•΄ λ°°μ—΄ fibArr을 μ‚¬μš©ν•©λ‹ˆλ‹€.

static void fibonacci(int n ){
	// ν”Όλ³΄λ‚˜μΉ˜ 수의 μ²«λ²ˆμ§Έμ™€ λ‘λ²ˆμ§ΈλŠ” 1이기 λ•Œλ¬Έμ— 1둜 μ„€μ •ν•΄ μ€λ‹ˆλ‹€.
    fibArr[0] = 1;
    fibArr[1] = 1;

    for( int i = 2; i<n; i++){
        fibArr[i] = fibArr[i-1] + fibArr[i-2];
        cnt++;
    }
}

 

πŸ“Œ

λ¬Έμ œμ—μ„œλŠ” 호좜 횟수λ₯Ό 묻고 있기 λ•Œλ¬Έμ— cntλ₯Ό 좜λ ₯ν•˜μ§€λ§Œ n번째의 ν”Όλ³΄λ‚˜μΉ˜ 수λ₯Ό 좜λ ₯ν•΄μ•Ό ν•œλ‹€λ©΄ fibArr[n-1]을 톡해 얻을 수 μžˆμŠ΅λ‹ˆλ‹€. ( n=5 일 λ•Œ fibArr = { 1, 1, 2, 3, 5 } )

λ˜ν•œ 문제의 λ²”μœ„κ°€ n(5<= n <= 40)이기 λ•Œλ¬Έμ— μƒκ΄€μ—†μ§€λ§Œ λ§Œμ•½ λ²”μœ„κ°€ 1λΆ€ν„° μ‹œμž‘μ΄λΌλ©΄ ArrayIndexOutOfBoundsException이 λ°œμƒν•  수 있기 λ•Œλ¬Έμ— μ΅œλŒ€ λ²”μœ„λ‘œ λ©”λͺ¨μ΄μ œμ΄μ…˜ ν•  배열을 μ„ μ–Έν•΄μ£Όλ©΄ ν•΄κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€. ( fibArr = new int[μ΅œλŒ€λ²”μœ„] )

 

πŸ”Ž 전체 μ½”λ“œ

import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.StringBuilder;

public class Main{
    
    static int n, cnt;
    static int[] fibArr;
    
	public static void main(String[] args) throws IOException {
       	BufferedReader br = new BufferedReader( new InputStreamReader(System.in));
        StringBuilder sb = new StringBuilder();
        
        n = Integer.parseInt(br.readLine());
        fibArr = new int[n];
        
        cnt = 0;
        fib(n);
        sb.append(cnt).append(" ");
        
        cnt = 0;
        fibonacci(n);
        sb.append(cnt);
        
        System.out.print(sb);
        br.close();
	}
    
    static int fib(int n){
        if( n == 1 || n == 2){
            cnt++;
            return 1;
        } else {
            return fib(n -1) + fib(n - 2);
        }
    }
    
    static void fibonacci(int n ){
        fibArr[0] = 1;
        fibArr[1] = 1;
        
        for( int i = 2; i<n; i++){
            fibArr[i] = fibArr[i-1] + fibArr[i-2];
            cnt++;
        }
    }
}

 

 

 


 

 

πŸ“š Reference : https://velog.io/@boyeon_jeong/%EB%8F%99%EC%A0%81%EA%B3%84%ED%9A%8D%EB%B2%95Dynamic-Programming