안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

 

이번 포스팅은 백준 10844번의 계단 오르기 문제를 해결한 과정을 정리하고자 합니다.

 

해당 문제는 N자리 숫자의 계단 수를 구하는 문제입니다.

 

1. 문제 해결 과정

1) n == 1일 때,

- 한자리 숫자의 경우 1 ~ 9까지 총 9개가 계단수입니다.

 

2) n == 2 일 때,

- 두 자리 숫자의 경우,  총 17개입니다.

12, 23, 34, 45, 56, 67, 78, 89 (8개)

10, 21, 32, 43, 54, 65, 76, 87, 98 (9개)

 

3) n == 3일 때,

- 세 자리 숫자의 경우, 총 32개입니다.

123, 234, 345, 456, 567, 678, 789 (7개)

987, 876, 765, 654, 543, 432, 321, 210 (8개)

101, 212, 323, 434, 545, 656. 767, 878, 989 (9개)

121, 232, 343, 454, 565, 676, 787, 898 (8개)

 

이 문제에서는 n번째 자리에 위치한 숫자는

(n - 1) 번째 자리 숫자보다 1 작은 수가 나올 수 있는 경우의 수  + (n -1) 번째 자리 숫자보다 1 큰 수가 나올 수 있는 경우의 수로

DP를 구현할 수 있습니다.

이를 그림으로 확인하면 다음과 같습니다.

따라서, 이를 알고리즘화 하면 아래의 코드로 구현할 수 있습니다. 

stair[i][j] = stair[i - 1][j - 1] + stair[i - 1][j + 1]

 

2. 주의할 점 

하지만, 주의할 점은 0보다 작은 자연수는 없으므로 자리 숫자에 0이 있으면 경우의 수는 (위치한 자릿수 - 1)의 1 큰 수의 경우만 고려해야 합니다.

stair[i][0] = stair[i - 1][1]

또한, 9는 9보다 1이 큰 한자리 숫자는 없으므로, (위치한 자리수 - 1)의 1 작은 수의 경우만 고려해야 합니다.

stair[i][9] = stair[i - 1][8]

 

마지막으로, 해당 알고리즘 결과를 제출하는데 여러 번 오답이 발생했습니다.

그 이유는, 최종 결과를 도출하는 과정에서 중간에 이미 long의 범위를 벗어나서 올바른 값을 도출하지 못했기 때문입니다.

(자바 long의 범위는 -2^63 ~ 2^63 -1)입니다.

 

해결책은 모듈러 연산을 적용하는 것입니다.

 

모든 경우의 수는 마지막 자릿수의 1부터 ~ 9까지의 경우를 더하면 완성이 됩니다. 이후, 10억으로 나눠도 되지만,

연산 가능 범위를 벗어날 수 있으므로, 모듈러 연산을 적용하여 각 배열에 저장할 때마다 MOD로 나머지 연산을 해주고 

최종적으로 마지막 숫자들을 더한 후, 다시 MOD로 나머지 연산을 해주면 동일한 결과를 얻을 수 있습니다.

        long result = 0L;
        for (int i = 0; i < 10; i++) {
            result += stair[N][i];
        }
(A + B) % p = ((A % p) + (B % p)) % p

 

3. 최종 소스 코드

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

public class Main10844 {

    static final long MOD = 1000000000;
    static int N;
    static long[][] stair;

    public static void main(String[] args) throws IOException {

        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));

        N = Integer.parseInt(bf.readLine());
        stair = new long[N + 1][10]; // (n 자리수), (0 ~ 9)

        long result = doStairDp();
        System.out.print(result % MOD);
    }

    static long doStairDp() {

        for (int i = 1; i<10; i++) {
            stair[1][i] = 1; // 첫번째 자리수가 i 일때는 모두 1
        }

        for (int i = 2; i <= N; i++) {

            for (int j = 0; j < 10; j++) {

                if (j == 0) {
                    stair[i][0] = stair[i - 1][1] % MOD;
                }

                else if (j == 9) {
                    stair[i][9] = stair[i - 1][8] % MOD;
                }

                else {
                    stair[i][j] = stair[i - 1][j - 1] + stair[i - 1][j + 1] % MOD;
                }
            }
        }

        long result = 0L;
        for (int i = 0; i < 10; i++) {
            result += stair[N][i];
        }

        return result;
    }
}

이상으로, 백준 10844 계단 오르기 문제 해결 과정 정리를 마치겠습니다.

감사합니다.!

 

문제 출처:

https://www.acmicpc.net/problem/10844

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

이번 포스팅은 백준 사이트의 2579번 계단 오르기 문제를 해결한 과정을 정리하려고 합니다.

 

해당 문제는 다음과 같습니다.

 

계단 오르기 게임은 계단 아래 시작점부터 계단 꼭대기에 위치한 도착점까지 가는 게임이다. <그림 1>과 같이 각각의 계단에는 일정한 점수가 쓰여 있는데 계단을 밟으면 그 계단에 쓰여 있는 점수를 얻게 된다.

 

<문제 규칙>

  1. 계단은 한 번에 한 계단씩 또는 두 계단씩 오를 수 있다. 즉, 한 계단을 밟으면서 이어서 다음 계단이나, 다음 다음 계단으로 오를 수 있다.
  2. 연속된 세 개의 계단을 모두 밟아서는 안 된다. 단, 시작점은 계단에 포함되지 않는다.
  3. 마지막 도착 계단은 반드시 밟아야 한다.

해당 문제는 DP를 활용하여 해결할 수 있습니다.

 

1. 문제 풀이 과정

 

밟은 계단은 파랑색, 안 밟은 계단은 회색으로 처리하였습니다.

 

  • if (n == 1) ? 
    n == 1이면, 무조건 계단을 밟아야 하므로, 10입니다.

 

  • if (n == 2) ?
    n == 2이면, 1번과 2번 계단을 밟아야 최대이므로, 답은 35입니다.

  • if ( n == 3)?
    n == 3이면, 1번 계단과 2번 계단 중, 큰 값을 선택하여 밟은 후, 3번째 계단을 밟아야 합니다.

  • if (n >= 4)?
    DP가 적용되기 시작하는 과정이라고 볼 수 있습니다.

 

  • if (n == 4)
    • 최댓값이라고 가정하여 밟을 수 있는 가지수는 크게 두가지 입니다. 연속해서 3개의 계단을 밟을 수 없으므로,
    • 1) 1번 + 3번 + 4번 => 10 + 15 + 25 = 50
    • 2) 1번 + 2번 + 4번 => 10 + 20 + 25 = 55
    • 즉, 1번 + 2번 + 4번을 밟는다면 55의 점수를 얻습니다.

 

  • if (n==5)
    • 1) 1번 + 2번 + 4번 + 5번 => 10 + 20 + 25 + 10 = 65
    • 2) 2번 + 3번 + 5번 => 20 + 25 + 10 = 55

 

이를 활용하여, 규칙을 확인할 수 있습니다.

만약 i 번째 계단이 있다면,

계단은 (i - 3) 번째의 얻을 수 있는 점수의 최대값 + (i - 1) 번째의 계단의 점수를 더한 값과 (i - 2) 번째의 점수의 최댓값을

비교하여 i번째 계단의 값을 더하면 최대 점수의 값을 구할 수 있습니다.

따라서, 30 + 25 + 10인 65가 됩니다.

 

따라서, 주요 로직은 다음과 같습니다. (scores는 i번째의 계단의 최대값을 기록하는 dp 스코어 배열, arr은 각 계단의 점수 배열)

scores[i] = Math.max(scores[i - 3] + arr[i - 1], scores[i - 2]) + arr[i];

 

2. 자바 알고리즘 코드 

최종 코드는 다음과 같습니다.

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

public class Main2579 {

    static int n;
    static int[] arr;
    static int[] scores;

    public static void main(String[] args) throws IOException {

        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));

        n = Integer.parseInt(bf.readLine()); // 입력받는 개수
        arr = new int[n]; // 입력받는 배열
        scores = new int[n]; // 점수 dp 스코어

        for (int i = 0; i < n; i++) {
            arr[i] = Integer.parseInt(bf.readLine());
        }

        long result = calculateDpScore();
        System.out.print(result);

    }

    static long calculateDpScore() {

        if (n == 1) {
            return arr[0];
        }

        else if (n == 2) {
            return arr[0] + arr[1];
        }

        scores[0] = arr[0];
        scores[1] = arr[0] + arr[1];
        scores[2] = Math.max(arr[0], arr[1]) + arr[2];

        for (int i = 3; i < n; i++) {
            scores[i] = Math.max(scores[i - 3] + arr[i - 1], scores[i - 2]) + arr[i];
        }

        return scores[n-1];
    }

}

이상으로 백준 2579번의 계단 오르기 알고리즘 정리를 마치겠습니다.

감사합니다.!!!

 

문제:

https://www.acmicpc.net/problem/2579

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

 

오늘 오전에는 BaekJoon의 우선순위 큐 문제를 풀어보았습니다. 이번 포스팅은 우선순위 큐의 정의, Comparable - comparator

백준 11286번 문제 해결 과정을 정리하도록 하겠습니다.

 

문제 출처: https://www.acmicpc.net/problem/11286

 

11286번: 절댓값 힙

첫째 줄에 연산의 개수 N(1≤N≤100,000)이 주어진다. 다음 N개의 줄에는 연산에 대한 정보를 나타내는 정수 x가 주어진다. 만약 x가 0이 아니라면 배열에 x라는 값을 넣는(추가하는) 연산이고, x가 0

www.acmicpc.net

 

1. Priority Queue의 특징

  • 높은 우선순위의 요소를 먼저 꺼내서 처리하는 구조
  • 내부 요소는 힙으로 구성되어 이진트리 구조로 이루어져 있습니다.
  • 내부구조가 힙으로 구성되어 있기에 시간 복잡도는 O(nlogn)
  • PriorityQueue에 객체를 추가하는 방법은 아래 표와 같습니다.
  예외 발생 값 리턴
추가 add offer (큐가 가득찬 경우 추가 실패 -> false)
삭제 remove poll
rjatk element peek

https://gmlwjd9405.github.io/2018/05/10/data-structure-heap.html

 

2 자료 구조 힙이란?

완전 이진트리의 일종으로 우선순위 큐를 위하여 만들어진 구조입니다.

여러 개의 값들 중에서 최댓값이나 최솟값을 빠르게 찾아내도록 만들어진 자료구조입니다.

힙은 반정렬(느슨한 정렬 상태)을 유지합니다.

-> 부모 노드의 키 값이 자식 노드의 키 값보다 항상 큰(작은) 이진트리입니다. 힙 트리는 중복된 값을 허용합니다.

 

https://gmlwjd9405.github.io/2018/05/10/data-structure-heap.html

자바 PriorityQueue에서 Heap 구현하기

자바는 PriorityQueue를 구현하기 위해 라이브러리를 호출해야 합니다. 만약 기본 타입이나, Wrapper 변수의 경우, 기본 정렬 방식을 사용할 수 있지만, 용자 정의 클래스 객체에 우선순위 큐를 적용하기 위해서는 비교(정렬) 방식을 정의해야 합니다. 따라서, 해당 문제에서는 Comparable 인터페이스를 구현하였습니다.

 

import java.util.PriorityQueue

 

Comparable과 Comparator 차이점

 

아래 블로그에 두 클래스에 대한 차이점을 명확하게 설명되어 있어서 참조 링크를 남기겠습니다.

요약하면, Comparable은 자기 자신과 매개변수 객체를 비교하는 것이고,

Comparator는 매개변수로 들어오는 두 객체를 비교하는 것입니다.

참조: https://st-lab.tistory.com/243

 

3. 문제 풀이

해당 문제는 절댓값 힙을 이용하여 입력값이 0인 경우, 절댓값이 가장 작은 값을 출력하고, x 이외의 숫자가 입력되면 힙에 추가하는 방식을 구현하는 문제입니다.

 

저는 위에 정리한 PriorityQueue와 Comparable 인터페이스를 구현하는 방법과 Comparator를 람다식으로 활용하여 해당 문제를 해결하였습니다. 특이점은,

AbsNumber 객체는 절댓값 힙 내에서만 사용되는 객체라 판단하였고, 해당 필드를 Main 클래스 내에서 사용하기 위하여 static 클래스로 선언하였습니다.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.PriorityQueue;


public class Main {

    static class AbsNumber implements Comparable<AbsNumber>{
        private int num;

        public AbsNumber (int num) {
            this.num = num;
        }

        @Override
        public int compareTo(AbsNumber absNumber) {

            if (Math.abs(this.num) == Math.abs(absNumber.num)) {
                return Integer.compare(this.num, absNumber.num);
            }
            return Integer.compare(Math.abs(this.num), Math.abs(absNumber.num));
        }
    }

    public static void main(String[] args) throws IOException {

        PriorityQueue<AbsNumber> absHeap = new PriorityQueue<>();
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        int n = Integer.parseInt(reader.readLine());

        for (int i = 0; i < n; i++) {
            int x = Integer.parseInt(reader.readLine());

            if (x == 0) {
                if (absHeap.isEmpty()) {
                    System.out.println(0);
                } else {
                    System.out.println(absHeap.poll().num);
                }
            } else {
                absHeap.add(new AbsNumber(x));
            }
        }
    }

}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.PriorityQueue;


public class Main11286_2 {

    static class AbsNumber {
        private int num;

        public AbsNumber (int num) {
            this.num = num;
        }
    }

    public static void main(String[] args) throws IOException {

        PriorityQueue<AbsNumber> absHeap = new PriorityQueue<>(
                (absNumber, t1) -> {

                    if (Math.abs(absNumber.num) == Math.abs(t1.num)) {
                        return absNumber.num - t1.num;
                    }
                    return Math.abs(absNumber.num) - Math.abs(t1.num);
                }
        );

        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        int n = Integer.parseInt(reader.readLine());

        for (int i = 0; i < n; i++) {
            int x = Integer.parseInt(reader.readLine());

            if (x == 0) {
                if (absHeap.isEmpty()) {
                    System.out.println(0);
                } else {
                    System.out.println(absHeap.poll().num);
                }
            } else {
                absHeap.add(new AbsNumber(x));
            }
        }
    }

}

두 해결방식의 차이점은 AbsNumber라는 객체를 객체 중심적으로 보고 해결하느냐, 혹은 해당 알고리즘에 특화된 객체라고 판단하고 해결하는가? 두 의도에 따라 달라진다고 생각합니다.

 

전자는. 내부 클래스에 선언했으므로 해당 AbsNumber를 이 과제에 특화되어 사용되는 클래스라고 판단하였기 때문에, Comparable 인터페이스를 구현하였습니다.

 

후자는, AbsNumber를 Comparable을 구현한 클래스로 만들면,  AbsNumber가 정렬 방식에 의존하게 된다는 생각이 들었습니다.  따라서, AbsNumber는 '절댓값의 수'라는 본질의 객체를 남겨두고,  과제를 해결하는 메소드 내에서 PriorityQueue에 사용할 정렬 방식을 Comparator<AbsNumber> 익명 내부 클래스 -> 람다로 전환하여 사용하였습니다.

 

감사합니다.!

+ Recent posts