Coin Change - LeetCode

 

amount를 만드는데 필요한 최소 코인 개수를 찾아야 한다.

coins = [1, 2, 5], amount = 11 일 때

11 = 5 + 5 + 1 이 최소이다. 답은 3이다.

coins = [2], amount = 3 일 때

3은 2를 통해 만들 수 없기 때문에 -1이 답니다.

coins = [1], amount = 0이면 0개를 사용하면 되기 때문에 답은 0이다.

우선 greedy를 고려해 볼 수 있지만 각 동전은 대체가 불가능하기 때문에 최적해를 보장할 수 없다.

dp를 생각해 보자.

dp [i] = i원을 만드는데 필요한 최소 동전 개수이다.

첫 번째 예시에서 dp [11] = min(dp [6], dp [9], dp [10]) + 1 이렇게 된다.

정리하자면 dp [i] = min(dp [i], dp [i-coin] + 1), for coin in coins, dp [0] = 0이 된다.

class Solution {
    public int coinChange(int[] coins, int amount) {
        int INF = Integer.MAX_VALUE;
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, INF);
        dp[0] = 0;

        for (int i = 1; i <= amount; i++) {
            for (int coin : coins) {
                if (i - coin >= 0 && dp[i-coin] != INF) {
                    dp[i] = Math.min(dp[i], dp[i - coin] + 1); 
                }
            }
        }

        return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
    }
}

'Algorithm > LeetCode' 카테고리의 다른 글

LeetCode 202 - Happy Number  (1) 2024.11.21
LeetCode 155 - Min Stack  (0) 2024.11.02
LeetCode 74 - Search a 2D Matrix  (1) 2024.10.30
LeetCode 66 - Plus One  (0) 2024.10.25
LeetCode 198 - House Robber  (0) 2024.10.25

Min Stack - LeetCode

 

stack을 구현하는 문제이다. 모든 작업이 O(1)의 시간복잡도로 가능해야 한다.

 

대부분의 기능은 stack을 쓰면 O(1)이 가능하지만 최솟값을 알아내야 하는 기능도 O(1)로 동작해야 한다는 점이 중요하다.

우선 최소값을 변수로 저장하는 방법을 생각해 보자.

이 방법은 최솟값을 바로 찾을 수는 있지만 최솟값이 갱신될 때 스택 내의 모든 요소를 순회해야 한다.

별도로 해당 레벨에서의 최솟값을 가지고 있어야 할 객체가 필요하다.

즉 삽입되는 값을 노드라고 생각했을 때 항상 노드 기준 최소값을 함께 기억하고 있는 것이다.

class MinStack {

    static class Node {
        int val;
        int min;

        public Node(int val, int min) {
            this.val = val;
            this.min = min;
        }
    }

    private Deque<Node> stack = new LinkedList<>();

    public MinStack() {
    }
    
    public void push(int val) {
        if (!stack.isEmpty()) {
            Node top = stack.peekLast();
            stack.addLast(new Node(val, Math.min(val, top.min)));
            return;
        }
        stack.addLast(new Node(val, val));
    }
    
    public void pop() {
        stack.pollLast();
    }
    
    public int top() {
        return stack.peekLast().val;
    }
    
    public int getMin() {
        return stack.peekLast().min;
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(val);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

'Algorithm > LeetCode' 카테고리의 다른 글

LeetCode 202 - Happy Number  (1) 2024.11.21
LeetCode 322 - Coin Change  (1) 2024.11.03
LeetCode 74 - Search a 2D Matrix  (1) 2024.10.30
LeetCode 66 - Plus One  (0) 2024.10.25
LeetCode 198 - House Robber  (0) 2024.10.25

15591번: MooTube (Silver)

 

1 ~ N까지의 동영상 존재(1≤N≤5,000)

연관 동영상 리스트를 만들기로 했다.

연관성 측정 = “USADO”

N-1개의 동영상 쌍을 골라서 어떤 동영상에서 다른 동영상으로 가는 경로가 반드시 하나 존재.

임의의 두 쌍 사이의 동영상의 USADO = 그 경로의 모든 연결들의 USADO 중 최솟값

주어진 동영상에 대해 USADO가 K이상인 모든 동영상이 추천되도록 하려고 한다. 이때의 적절한 K값을 결정하려 한다.

USADO가 k일 때 i번 영상을 보고 있는 소들에게 몇 개의 동영상이 추천될지 출력하다.

우선 주어진 입력값 중 USADO인 r의 범위가 1≤r≤1,000,000,000이다. 10억으로 매우 크다.

동영상이 노드이며 양방향 가중치가 있는 그래프이다.

예제 입력을 보자.

4 3
1 2 3
2 3 2
2 4 4
1 2
4 1
3 1

일 때 1번과 2번은 USADO 3, 2번과 3번은 USADO 2, 2번과 4번은 USADO 4를 가지고 있다.

여기서 1번과 3번의 USADO를 계산해 보면 min(2, 3) = 2가 된다.

해당 경로에 있는 USADO 중 최소 값이다.

여기서 질문 4 1 이면 1번 기준 모든 USADO가 필요하다.

(1,2) = 3, (1,3) = 2, (1,4) = 3이며 k가 4이기 때문에 4 이상인 USADO가 없으므로 0개가 추천이 된다.

시작점부터 시작해 도달할 수 있는 모든 노드 사이에 비용이 k 이상인 것들의 개수를 세면 된다.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main {

    static class Edge {
        int to;
        int distance;

        Edge(int to, int distance) {
            this.to = to;
            this.distance = distance;
        }
    }

    static FastReader scan = new FastReader();
    static final int INF = Integer.MAX_VALUE;
    static int N, Q;
    static ArrayList<Edge>[] graph;
    static int[][] questions;

    static void input() {
        N = scan.nextInt();
        Q = scan.nextInt();
        graph = new ArrayList[N];
        for (int i = 0; i < N; i++) {
            graph[i] = new ArrayList();
        }
        for (int i = 0; i < N - 1; i++) {
            int p = scan.nextInt() - 1;
            int q = scan.nextInt() - 1;
            int w = scan.nextInt();
            graph[p].add(new Edge(q, w));
            graph[q].add(new Edge(p, w));
        }

        questions = new int[Q][];

        for (int i = 0; i < Q; i++) {
            questions[i] = new int[]{scan.nextInt(), scan.nextInt() - 1}; //k, v
        }
    }

    static int bfs(int start, int k) {
        boolean[] visited = new boolean[N];
        Queue<Integer> q = new LinkedList<>();
        q.add(start);
        visited[start] = true;
        int count = 0;

        while (!q.isEmpty()) {
            int curr = q.poll();

            for (Edge edge : graph[curr]) {
                if (!visited[edge.to]) {
                    int minUsado = Math.min(edge.distance, k);
                    if (minUsado >= k) {
                        visited[edge.to] = true;
                        q.add(edge.to);
                        count++;
                    }
                }
            }
        }
        return count;
    }

    static void solve() {
        for (int[] q : questions) {
            int k = q[0];
            int v = q[1];
            System.out.println(bfs(v, k));
        }
    }

    public static void main(String[] args) throws IOException {
        input();
        solve();
    }

    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreTokens()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }
    }
}

'Algorithm > BOJ' 카테고리의 다른 글

BOJ 2292 - 벌집  (5) 2024.11.06
BOJ 2933 - 미네랄  (0) 2024.11.05
BOJ 10830 - 행렬의 제곱  (0) 2024.10.31
BOJ 12865 - 평범한 배낭  (0) 2024.10.31
BOJ 15989 - 1,2,3 더하기 4  (0) 2024.10.26

10830번: 행렬 제곱

 

N*N 크기의 행렬 A, A의 B제곱을 구하라. 수가 매우 커질 수 있으니 A^B의 각 원소를 1,000으로 나눈 나머지를 출력.

2≤N≤5, 1≤B≤100,000,000,000

B가 아주 크다. 단순한 방법으로는 안된다.

행렬 A의 B제곱은 A의 B-1 제곱에 A를 곱한 것이다. 팩토리얼과 유사하다. 그런데 B가 최대이면 단순하게 순회를 1번만 돌아도 시간초과이다.

B가 100만이라고 해보자 A^100만 = (A^50만)^2

$$ A^B = {A^{(B/2)}}^2 $$

이러면 B가 1천억이라 하더라도 500억 → 250억 → 125억 이렇게 logN으로 감소시킬 수 있다.

그런데 B가 홀수냐 짝수냐에 따라서 처리를 좀 달리해야 한다. B가 짝수라면 그냥 해도 되지만 B가 홀수라면 1일 빼서 짝수로 만든 다음 해당 과정을 처리하고 마지막에 하나를 다시 곱해준다.

필요한 연산들은 다음과 같다.

  1. 행렬의 곱셈
  2. 거듭제곱의 재귀
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {

    static int N;
    static int[][] matrix;
    static long B;
    static final int MOD = 1_000;

    static FastReader scan = new FastReader();

    static void input() {
        N = scan.nextInt();
        B = scan.nextLong();
        matrix = new int[N][N];

        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                matrix[i][j] = scan.nextInt() % MOD;
            }
        }
    }

    static int[][] multiply(int[][] A, int[][] B) {
        int[][] C = new int[N][N];

        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                for (int k = 0; k < N; k++) {
                    C[i][j] = (C[i][j] + (A[i][k] * B[k][j]) % MOD) % MOD;
                }
            }
        }
        return C;
    }

    static int[][] power(int[][] A, long B) {
        if (B == 1) {
            return A;
        }

        int[][] tmp = power(A, B / 2);
        tmp = multiply(tmp, tmp);

        if (B % 2 == 1) {
            tmp = multiply(tmp, matrix);
        }

        return tmp;
    }

    static void solve() {
        int[][] result = power(matrix, B);

        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                System.out.print(result[i][j] + " ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        input();
        solve();
    }

    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreTokens()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }

        long nextLong() {
            return Long.parseLong(next());
        }
    }
}

'Algorithm > BOJ' 카테고리의 다른 글

BOJ 2933 - 미네랄  (0) 2024.11.05
BOJ 15591 - MooTube (Silver)  (0) 2024.10.31
BOJ 12865 - 평범한 배낭  (0) 2024.10.31
BOJ 15989 - 1,2,3 더하기 4  (0) 2024.10.26
BOJ 1528 - 금민수의 합  (0) 2024.10.26

12865번: 평범한 배낭

 

N개의 물건이 있다. 각 물건은 무게 W와 가치 V를 가진다.

최대 K만큼의 무게만 넣을 수 있는 배낭이 있을 때 가지고 갈 수 있는 물건들의 가치의 최댓값을 찾아라

1≤N≤100, 1≤K≤100,000, 1≤W≤100,000, 0≤V≤1,000

 

단위 무게당 가장 가치있는 물건을 넣어볼까 생각할 수 있다. 하지만 그러면 안 된다. 물건을 쪼갤 수 없을뿐더러 해당 경우가 최선의 경우인지 보장이 안된다.

완전탐색의 경우에는 해당 물건을 넣냐 안넣냐의 경우가 존재해 O(2^100)으로 불가능하다

결국 부분 문제의 해를 이용하는 DP를 써야한다.

dp 배열의 의미부터 정해보자. 물건을 넣냐 안 넣냐 와 무게 이 두 가지가 필요하다.

즉 dp[i][w] = i번 째 물건까지 고려했을 때, 무게 w를 사용해서 얻을 수 있는 최대 가치가 된다.

i 번 째 물건을 넣을 수 있는 경우가 넣을 수 없는 경우가 존재한다.

i번 째 물건을 넣을 수 있는데 넣는 경우와 안 넣는 경우도 존재한다.

i 번 째 물건을 넣을 수 있는 경우는 다음과 같다.

dp[i][w] = Math.max(dp[i-1][w], dp[i-1][w - weight[i]] + value[i])가 된다.

i 번 째 물건을 넣을 수 없는 경우는 다음과 같다.

dp[i][w] = dp[i-1][w]

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

public class Main {

    static class Item {
        int weight;
        int value;

        Item(int weight, int value) {
            this.weight = weight;
            this.value = value;
        }
    }

    static FastReader scan = new FastReader();
    static int N;
    static int K;
    static Item[] items;

    static void input() {
        N = scan.nextInt();
        K = scan.nextInt();
        items = new Item[N + 1];
        for (int i = 1; i <= N; i++) {
            Item item = new Item(scan.nextInt(), scan.nextInt());
            items[i] = item;
        }
    }

    static void solve() {
        int[][] dp = new int[N + 1][K + 1];
        for (int i = 1; i <= N; i++) {
            for (int w = 1; w <= K; w++) {
                if (items[i].weight <= w) {
                    // 해당 아이템을 넣을 수 있는 경우
                    dp[i][w] = Math.max(dp[i - 1][w], dp[i - 1][w - items[i].weight] + items[i].value);
                } else {
                    dp[i][w] = dp[i - 1][w];
                }
            }
        }
        System.out.println(dp[N][K]);
    }

    public static void main(String[] args) {
        input();
        solve();
    }

    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreTokens()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }
    }
}

'Algorithm > BOJ' 카테고리의 다른 글

BOJ 15591 - MooTube (Silver)  (0) 2024.10.31
BOJ 10830 - 행렬의 제곱  (0) 2024.10.31
BOJ 15989 - 1,2,3 더하기 4  (0) 2024.10.26
BOJ 1528 - 금민수의 합  (0) 2024.10.26
BOJ 1655 - 가운데를 말해요  (0) 2024.10.25

코딩테스트 연습 - 가장 큰 정사각형 찾기 | 프로그래머스 스쿨

 

프로그래머스

SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

1과 0으로 이루어진 2차원 배열 board에서 1로 이루어진 가장 큰 정사각형을 찾아 넓이를 반환

먼저 떠오르는 방식은 완전 탐색이다. 모든 가능한 정사각형의 크기를 다 확인하는 것이다. 하지만 이는 시간복잡도 O(n^2m^2)으로 비효율적이다.

따라서 dp를 생각해 보자.

dp[i][j]에서의 최대 정사각형의 크기는 어떻게 판단할까?

dp[i][j]에서의 최대 정사각형은 min(dp[i-1][j], dp[i-1][j-1], dp[i][j-1]) + 1이 된다.

import java.util.*;

class Solution
{
    public int solution(int [][]board)
    {
        for (int i = 1; i < board.length; i++) {
            for (int j = 1; j < board[i].length; j++) {
                if (board[i][j] == 1) {
                    board[i][j] += Math.min(board[i - 1][j], Math.min(board[i][j - 1], board[i - 1][j - 1]));
                }
            }
        }
        
        int x = Arrays.stream(board).flatMapToInt(Arrays::stream).max().getAsInt();

        return x * x;
    }
}

'Algorithm > Programmers' 카테고리의 다른 글

Programmers 42897 - 도둑질  (0) 2024.10.30
BOJ 2295 - 세 수의 합  (0) 2024.10.26
Programmers 132265 - 롤케이크 자르지  (1) 2024.10.24
Programmers 42577 - 전화번호 목록  (0) 2024.10.24
Programmers 62050 - 지형 이동  (0) 2024.10.23

코딩테스트 연습 - 도둑질 | 프로그래머스 스쿨

 

프로그래머스

SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

인접한 집을 털면 경보가 울린다.

그리고 집이 원형으로 배치되어 있다.

돈이 담긴 배열 money가 주어질 때, 도둑이 훔칠 수 있는 돈의 최대 값은?

이 두 가지 특징이 있는 상황이다.

핵심은 원형 구조이다. 따라서 1번 집이 제일 중요해진다. 1번 집을 털게 되면 마지막 집을 털 수 없게 된다. 1번 집을 털지 않는다면 마지막 집을 털 수 있게 된다.

dp [i]를 i번째 위치에서의 최댓값이라 생각해 보자. 이 값은 i-2를 털고 i를 터는 경우와 i-1을 털고 i를 안터는 경우 두 가지 중 큰 값이 될 것이다.

그런데 앞서 말했듯이 1번집부터 털지 안 털지 별개의 배열로 관리해야 한다.

import java.util.*;

class Solution {
    public int solution(int[] money) {
        int n = money.length;
        int[] dp1 = new int[n + 1];
        int[] dp2 = new int[n + 1];
        dp1[0] = money[0];
        dp1[1] = money[0];
        dp2[0] = 0;
        dp2[1] = money[1];
        
        for (int i = 2; i < n - 1; i++) {
            // 첫 집을 터는 경우
            dp1[i] = Math.max(dp1[i - 2] + money[i], dp1[i - 1]);
        }
        
        for (int i = 2; i < n; i++) {
            dp2[i] = Math.max(dp2[i - 2] + money[i], dp2[i - 1]);
        }
        
        return Math.max(dp1[n - 2], dp2[n - 1]);
    }
}

Search a 2D Matrix - LeetCode

 

m x n 2차원 배열인 matrix에 target에 존재하는지 확인하는 문제이다.

matrix에는 특징이 있는데 각각의 row는 오름차순으로 정렬 돼 있고 각 row의 첫 번째 값은 이전 row의 마지막 값보다 크다.

O(log(m*n))의 시간 복잡도로 해결해야 한다.

matrix의 조건을 보면 알 수 있듯이 결국 정렬된 상태라는 소리다.

flatMap으로 1차원으로 변경할 수 있으면 좋지만 시간복자도 제한 때문에 불가능하다. 그렇지만 이미 정렬된 상태이기 때문에 2D인 채로 이진 탐색이 가능하다.

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int m = matrix.length;
        int n = matrix[0].length;

        return binarySearch(matrix, 0, m * n - 1, target, m, n) < 0 ? false : true;
    }

    private int binarySearch(int[][] arr, int left, int right, int target, int m, int n) {
        if (left > right) return -1;

        int mid = left - (left - right) / 2;
        int value = arr[mid / n][mid % n];

        if (value == target) {
            return mid;
        } else if (value < target) {
            return binarySearch(arr, mid + 1, right, target, m, n);
        } else {
            return binarySearch(arr, left, mid - 1, target, m, n);
        }
    }
}

'Algorithm > LeetCode' 카테고리의 다른 글

LeetCode 322 - Coin Change  (1) 2024.11.03
LeetCode 155 - Min Stack  (0) 2024.11.02
LeetCode 66 - Plus One  (0) 2024.10.25
LeetCode 198 - House Robber  (0) 2024.10.25
LeetCode 130 - Surrounded Regions  (0) 2024.10.22

15989번: 1, 2, 3 더하기 4

 

정수 n을 1, 2, 3의 합으로 나타내는 방법을 구해라.

n ≤ 10,000

재귀를 통해 모든 것을 확인하기에는 너무 많다.

숫자가 1, 2, 3이니 합을 표현할 때 1, 2, 3으로 끝나는 경우를 나눠보자.

2차원 배열로 dp를 하자.

int [][] dp; → dp [i][j]는 i를 만들 때 마지막 수가 j인 경우를 이야기한다.

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

public class Main {

    static FastReader scan = new FastReader();

    public static void main(String[] args) {
        int T = scan.nextInt();
        int[][] dp = new int[10002][4];
        dp[1][1] = 1;
        dp[2][1] = 1;
        dp[2][2] = 1;
        dp[3][1] = 1;
        dp[3][2] = 1;
        dp[3][3] = 1;

        for (int i = 4; i <= 10000; i++) {
            dp[i][1] = 1;
            dp[i][2] = dp[i-2][1] + dp[i-2][2];
            dp[i][3] = dp[i-3][1] + dp[i-3][2] + dp[i-3][3];
        }

        for (int p = 0; p < T; p++) {
            int N = scan.nextInt();
            System.out.println(dp[N][1] + dp[N][2] + dp[N][3]);
        }
    }

    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreTokens()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }
    }
}

'Algorithm > BOJ' 카테고리의 다른 글

BOJ 10830 - 행렬의 제곱  (0) 2024.10.31
BOJ 12865 - 평범한 배낭  (0) 2024.10.31
BOJ 1528 - 금민수의 합  (0) 2024.10.26
BOJ 1655 - 가운데를 말해요  (0) 2024.10.25
BOJ 1944 - 복제 로봇  (2) 2024.10.23

1528번: 금민수의 합

 

금민수 = 4와 7로만 이루어진 수

숫자 N(N ≤ 1,000,000)을 금민수의 합으로 나타내라. 여러 방법이 존재한다면 수의 개수가 적은 것을 출력한다.

그러한 방법도 여러 개일 경우 사전 순으로 가장 앞서는 것을 출력.

표현할 수 없다면 -1

  1. 금민수의 합으로 나타내는 방법을 알아야 한다.
  2. 여러 방법이 존재하는지 확인하기 위해 저장을 해야 한다.
  3. 사전순으로 정렬은 문자열의 기본 정렬이다.

만약 N 보다 작은 금민수를 전부 찾아서 저장할 수 있을까? → 메모리상으로는 가능하다.

그런데 전부 찾아서 저장하더라도 해당 조합을 재귀로 찾기에는 너무 많다.

예를 들어 100을 생각해 보자. 100보다 작은 금민수는 4, 7, 44, 47, 74, 77이 존재한다.

100을 만들 수 있는가? = 23 or 26 or 56 or 93 or 96을 금민수의 조합으로 만들 수 있느냐

23 → 4, 7 → 19, 16을 만들 수 있는가

16 → 4, 7 → 12, 9를 만들 수 있는가

12 → 4, 7 → 8, 5를 만들 수 있는가

8 → 4, 7 → 4, 1을 만들 수 있는가

4 → 4 → 성공

DP다. 만들 수 없음을 -1을 두고 만들 수 있는 것은 최소 개수를 저장한다.

  1. 먼저 N보다 작은 금민수를 만든다.
  2. bottom - up으로 0부터 dp를 채운다.
  3. 최소개수가 더 적다면 갱신이 된다면 구성되는 숫자를 덮어 씌운다.
  4. 최소개수가 동일하다면 구성 숫자를 사전순으로 비교한 후 덮어 씌운다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;

public class Main {

    static FastReader scan = new FastReader();
    static int N;
    static final String[] nums = {"4", "7"};
    static List<Integer> fourSeven = new ArrayList<>();
    static final int INF = Integer.MAX_VALUE;

    static void input() {
        N = scan.nextInt();
    }

    static void getFourSeven(StringBuilder curr) {
        if (curr.length() > 0 && Integer.parseInt(curr.toString()) > N) {
            return;
        }

        if (curr.length() > 0 ) {
            fourSeven.add(Integer.parseInt(curr.toString()));
        }

        for (String num : nums) {
            curr.append(num);
            getFourSeven(curr);
            curr.deleteCharAt(curr.length() - 1);
        }
    }

    public static void main(String[] args) throws IOException {
        input();
        getFourSeven(new StringBuilder());
        Collections.sort(fourSeven);

        int[] dp = new int[N + 1];
        Arrays.fill(dp, INF);
        List<Integer>[] sequences = new ArrayList[N + 1];
        dp[0] = 0;
        sequences[0] = new ArrayList<>();

        for (int i = 0; i <= N; i++) {
            if (dp[i] == INF) continue;

            for (int num : fourSeven) {
                int next = i + num;
                if (next > N) continue;

                if (dp[i] + 1 < dp[next]) {
                    dp[next] = dp[i] + 1;
                    sequences[next] = new ArrayList<>(sequences[i]);
                    sequences[next].add(num);
                } else if (dp[i] + 1 == dp[next]) {
                    List<Integer> newSeq = new ArrayList<>(sequences[i]);
                    newSeq.add(num);

                    if (isFirst(newSeq, sequences[next])) {
                        sequences[next] = newSeq;
                    }
                }
            }
        }

        if (dp[N] == INF) {
            System.out.println(-1);
        } else {
            StringBuilder sb = new StringBuilder();
            for (int num : sequences[N]) {
                sb.append(num).append(" ");
            }
            System.out.println(sb.toString().trim());
        }
    }

    static boolean isFirst(List<Integer> newSeq, List<Integer> seq) {
        for (int i = 0; i < Math.min(newSeq.size(), seq.size()); i++) {
            if (!newSeq.get(i).equals(seq.get(i))) {
                return newSeq.get(i) < seq.get(i);
            }
        }
        return newSeq.size() < seq.size();
    }

    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreTokens()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }
    }
}

메모리 초과가 발생헀다.

 

dp 배열 크기는 4MB로 문제없지만 sequences의 실제 데이터에서 문제가 발생하는 것 같다.

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

public class Main {
    static FastReader scan = new FastReader();
    static int N;
    static final String[] nums = {"4", "7"};
    static List<Integer> fourSeven = new ArrayList<>();
    static final int INF = Integer.MAX_VALUE;

    static void input() {
        N = scan.nextInt();
    }

    static void getFourSeven(StringBuilder curr) {
        if (curr.length() > 0 && Integer.parseInt(curr.toString()) > N) {
            return;
        }

        if (curr.length() > 0) {
            fourSeven.add(Integer.parseInt(curr.toString()));
        }

        for (String num : nums) {
            curr.append(num);
            getFourSeven(curr);
            curr.deleteCharAt(curr.length() - 1);
        }
    }

    public static void main(String[] args) throws IOException {
        input();
        getFourSeven(new StringBuilder());
        Collections.sort(fourSeven);

        int[] dp = new int[N + 1];
        int[] prev = new int[N + 1];
        int[] used = new int[N + 1];
        Arrays.fill(dp, INF);
        Arrays.fill(prev, -1);
        dp[0] = 0;

        for (int i = 0; i <= N; i++) {
            if (dp[i] == INF) continue;

            for (int num : fourSeven) {
                int next = i + num;
                if (next > N) continue;

                if (dp[i] + 1 < dp[next]) {
                    dp[next] = dp[i] + 1;
                    prev[next] = i;
                    used[next] = num;
                } else if (dp[i] + 1 == dp[next]) {
                    List<Integer> currentPath = getPath(i, prev, used);
                    currentPath.add(num);
                    List<Integer> existingPath = getPath(next, prev, used);

                    if (isFirst(currentPath, existingPath)) {
                        prev[next] = i;
                        used[next] = num;
                    }
                }
            }
        }

        if (dp[N] == INF) {
            System.out.println(-1);
        } else {
            List<Integer> answer = getPath(N, prev, used);
            StringBuilder sb = new StringBuilder();
            for (int num : answer) {
                sb.append(num).append(" ");
            }
            System.out.println(sb.toString().trim());
        }
    }

    // 경로 복원 함수
    static List<Integer> getPath(int n, int[] prev, int[] used) {
        List<Integer> path = new ArrayList<>();
        while (n > 0) {
            path.add(used[n]);
            n = prev[n];
        }
        Collections.sort(path);
        return path;
    }

    static boolean isFirst(List<Integer> newSeq, List<Integer> seq) {
        for (int i = 0; i < Math.min(newSeq.size(), seq.size()); i++) {
            if (!newSeq.get(i).equals(seq.get(i))) {
                return newSeq.get(i) < seq.get(i);
            }
        }
        return newSeq.size() < seq.size();
    }

    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreTokens()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }
    }
}

위치를 저장하는 배열을 사용하도록 변경했다. 하지만 역시 메모리 초과가 발생했다…

뭐가 원인인지 파악하지 못해 풀이를 dp에서 다른 방식으로 찾아보기로 하였다.

 

BFS방식을 사용하도록 하겠다.

BFS는 레벨별 탐색 즉 사용한 금민수의 개수별로 탐색이 진행되기 때문에 원하는 답에 최소 개수로 도달할 수 있다. 또한 사용하는 금민수를 사전순으로 정리해 두면 자연스럽게 사전순으로 빠른 것을 먼저 찾는다.

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

public class Main {
    static FastReader scan = new FastReader();
    static int N;
    static final String[] nums = {"4", "7"};
    static List<Integer> fourSeven = new ArrayList<>();
    static final int INF = Integer.MAX_VALUE;

    static void input() {
        N = scan.nextInt();
    }

    static void getFourSeven(StringBuilder curr) {
        if (curr.length() > 0 && Integer.parseInt(curr.toString()) > N) {
            return;
        }

        if (curr.length() > 0) {
            fourSeven.add(Integer.parseInt(curr.toString()));
        }

        for (String num : nums) {
            curr.append(num);
            getFourSeven(curr);
            curr.deleteCharAt(curr.length() - 1);
        }
    }

    static class State {
        int sum;
        List<Integer> nums;

        State(int sum, List<Integer> nums) {
            this.sum = sum;
            this.nums = nums;
        }
    }

    public static void main(String[] args) throws IOException {
        input();
        getFourSeven(new StringBuilder());
        Collections.sort(fourSeven);

        Queue<State> queue = new LinkedList<>();
        boolean[] visited = new boolean[N + 1];

        queue.add(new State(0, new ArrayList<>()));
        visited[0] = true;

        while (!queue.isEmpty()) {
            State curr = queue.poll();

            if (curr.sum == N) {
                StringBuilder sb = new StringBuilder();
                for (int num : curr.nums) {
                    sb.append(num).append(" ");
                }
                System.out.println(sb.toString().trim());
                return;
            }

            for (int num : fourSeven) {
                int nextSum = curr.sum + num;

                if (nextSum > N || visited[nextSum]) continue;

                List<Integer> nextNums = new ArrayList<>(curr.nums);
                nextNums.add(num);

                queue.add(new State(nextSum, nextNums));
                visited[nextSum] = true;
            }
        }

        System.out.println(-1);
    }

    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreTokens()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }
    }
}

'Algorithm > BOJ' 카테고리의 다른 글

BOJ 12865 - 평범한 배낭  (0) 2024.10.31
BOJ 15989 - 1,2,3 더하기 4  (0) 2024.10.26
BOJ 1655 - 가운데를 말해요  (0) 2024.10.25
BOJ 1944 - 복제 로봇  (2) 2024.10.23
BOJ 15684 - 사다리 조작  (0) 2024.10.11

+ Recent posts