문제 링크

분류

그래프 이론, 그래프 탐색, 너비 우선 탐색, 깊이 우선 탐색

 

문제 설명

정사각형으로 이루어져 있는 섬과 바다 지도가 주어진다. 섬의 개수를 세는 프로그램을 작성하시오.

한 정사각형과 가로, 세로 또는 대각선으로 연결되어 있는 사각형은 걸어갈 수 있는 사각형이다.

두 정사각형이 같은 섬에 있으려면, 한 정사각형에서 다른 정사각형으로 걸어서 갈 수 있는 경로가 있어야 한다. 지도는 바다로 둘러싸여 있으며, 지도 밖으로 나갈 수 없다.

 

입력

입력은 여러 개의 테스트 케이스로 이루어져 있다. 각 테스트 케이스의 첫째 줄에는 지도의 너비 w와 높이 h가 주어진다. w와 h는 50보다 작거나 같은 양의 정수이다.

둘째 줄부터 h개 줄에는 지도가 주어진다. 1은 땅, 0은 바다이다.

입력의 마지막 줄에는 0이 두 개 주어진다.

 

출력

각 테스트 케이스에 대해서, 섬의 개수를 출력한다.

 

풀이 (DFS 그래프 탐색)

시간 복잡도 O(n*m) 공간 복잡도 O(n*m)

아이디어: 전체 배열의 요소를 차례대로 탐색하는데 만약 1인 지점이 나온다면 해당 값을 0으로 표시하고

상하좌우 대각선을 포함한 8가지 방향으로 dfs로 타고 들어가기 만약 0이거나 범위를 벗어나면 나오고 1이면 방금 전 했던 작업의 반복

결과적으로 우리는 한 개의 섬에대해 탐색을 마쳤을 때 섬이라 표시된 1의 값을 전부 0으로 바꿔 줌 으로써 

다음 탐색에 걸리지 않게 된다.

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

public class Main {
    static void dfs(int[][] map, int i, int j, int m, int n){
        // 범위를 벗어났거나 땅(0)이라면 반환
        if(i<0||i>=m||j<0||j>=n||map[i][j]==0)
            return;
        // 섬(1)으로 표시된 값 0으로 수정
        if(map[i][j]==1)
            map[i][j]=0;
        // 8가지 방향에 대해 차례대로 dfs 탐색
        dfs(map,i+1,j,m,n);
        dfs(map,i-1,j,m,n);
        dfs(map,i,j+1,m,n);
        dfs(map,i,j-1,m,n);
        dfs(map,i+1,j+1,m,n);
        dfs(map,i-1,j-1,m,n);
        dfs(map,i+1,j-1,m,n);
        dfs(map,i-1,j+1,m,n);
    }
    public static void main(String[] args)throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        while(true){
            StringTokenizer st = new StringTokenizer(br.readLine());
            int n = Integer.parseInt(st.nextToken());
            int m = Integer.parseInt(st.nextToken());
            if(n==0&&m==0) return;
            int ans = 0;
            int[][] map= new int[m][n];

            for(int i=0;i<m;i++){
                st = new StringTokenizer(br.readLine());
                for(int j=0;j<n;j++){
                    map[i][j] = Integer.parseInt(st.nextToken());
                }
            }
            // 일단 1이라면 섬이기 때문에 카운트 1증가 이후 로직 수행
            for (int i = 0; i <m; i++) {
                for(int j=0; j<n; j++){
                    if(map[i][j]==1){
                        ans++;
                        dfs(map,i,j,m,n);
                    }
                }
            }
            System.out.println(ans);
        }
    }
}

 

문제 링크

분류

너비 우선 탐색, 그래프 이론, 그래프 탐색

 

문제 설명

수빈이는 동생과 숨바꼭질을 하고 있다. 수빈이는 현재 점 N(0 ≤ N ≤ 100,000)에 있고, 동생은 점 K(0 ≤ K ≤ 100,000)에 있다. 수빈이는 걷거나 순간이동을 할 수 있다. 만약, 수빈이의 위치가 X일 때 걷는다면 1초 후에 X-1 또는 X+1로 이동하게 된다. 순간이동을 하는 경우에는 1초 후에 2*X의 위치로 이동하게 된다.

수빈이와 동생의 위치가 주어졌을 때, 수빈이가 동생을 찾을 수 있는 가장 빠른 시간이 몇 초 후인지 구하는 프로그램을 작성하시오.

 

입력

첫 번째 줄에 수빈이가 있는 위치 N과 동생이 있는 위치 K가 주어진다. N과 K는 정수이다.

 

출력

수빈이가 동생을 찾는 가장 빠른 시간을 출력한다.

 

실패한 풀이(BFS + DP 원인: 메모리 초과)

시간 복잡도 O(n) 공간 복잡도 O(n)

아이디어: DP풀이 방식을 적용하여 DP테이블을 모든 공간 만큼 생성시키고 수빈이가 각 칸에 도달 할 수 있는 모든 경우의 수를 체크 및 지속적으로 갱신하여 Math.min() 수빈이가 동생에 도달 하기 전까지 최대한 모든 칸에 도착 할 수 있는 최소 시간의 값을 재갱신 

큐에 수빈이가 도착 할 수 있는 모든 경우를 입력

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

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        Queue<ArrayList<Integer>> queue = new LinkedList<>();
        int[] map = new int[100001];
        Arrays.fill(map,200000);
        ArrayList<Integer> ans = new ArrayList<>();
        int n = Integer.parseInt(st.nextToken());
        int k = Integer.parseInt(st.nextToken());

        map[n] = 0;
        ArrayList<Integer> node = new ArrayList<>(List.of(n));
        queue.add(node);

        while(!queue.isEmpty()){
            ArrayList<Integer> cur = queue.poll();
            ArrayList<Integer> tmp = new ArrayList<>();
            for (Integer pos : cur) {
                if(pos+1<=100000) {
                    map[pos + 1] = Math.min(map[pos + 1], map[pos] + 1);
                    tmp.add(pos+1);
                }
                if(pos-1>=0) {
                    map[pos - 1] = Math.min(map[pos - 1], map[pos] + 1);
                    tmp.add(pos-1);
                }
                if(2*pos<=100000) {
                    map[pos * 2] = Math.min(map[pos * 2], map[pos] + 1);
                    tmp.add(pos*2);
                }
            }
            if(map[k]!=200000||tmp.isEmpty())
                break;

            queue.add(tmp);
        }

        System.out.println(map[k]);
    }
}

 

성공한 풀이(BFS)

시간 복잡도 O(n) 공간 복잡도 O(n)

아이디어: 수빈이가 각 칸에 도달 할 수 있는 모든 경우의 수를 체크 및 지속적으로 갱신하여 Math.min() 수빈이가 동생에 도달 하기 전까지 최대한 모든 칸에 도착 할 수 있는 최소 시간의 값을 재갱신 하는 기존 로직에서 

생각을 좀 더 해보면 현 사이클(시간:n) 이 아닌 그 이후 사이클(시간:n+1)에 도착을 할 것이기 때문에

굳이 모든 경우를 체크 할 필요 없이 만약 이미 해당 칸에 도착한 적이 있다면 큐에 추가하지 않기 때문에

불필요한 이동을 막아 실패한 풀이에 비해 메모리 낭비를 줄일 수 있다.

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

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        Queue<int[]> queue = new LinkedList<>();
        int[] map = new int[100001];
        Arrays.fill(map,-1);
        ArrayList<Integer> ans = new ArrayList<>();
        int n = Integer.parseInt(st.nextToken());
        int k = Integer.parseInt(st.nextToken());

        map[n] = 0;
        queue.add(new int[]{n, 0});

        while(!queue.isEmpty()){
            int[] current = queue.poll();
            int pos = current[0];
            int time = current[1];

            if (pos == k) {
                System.out.println(time);
                break;
            }

            if (pos - 1 >= 0 && map[pos - 1] == -1) {
                map[pos - 1] = time + 1;
                queue.add(new int[]{pos - 1, time + 1});
            }
            if (pos + 1 <= 100000 && map[pos + 1] == -1) {
                map[pos + 1] = time + 1;
                queue.add(new int[]{pos + 1, time + 1});
            }
            if (pos * 2 <= 100000 && map[pos * 2] == -1) {
                map[pos * 2] = time + 1;
                queue.add(new int[]{pos * 2, time + 1});
            }
        }
        System.out.println(map[k]);
    }
}

 

문제 링크

분류

다이나믹 프로그래밍

문제 설명

정수 4를 1, 2, 3의 합으로 나타내는 방법은 총 7가지가 있다. 합을 나타낼 때는 수를 1개 이상 사용해야 한다.

  • 1+1+1+1
  • 1+1+2
  • 1+2+1
  • 2+1+1
  • 2+2
  • 1+3
  • 3+1

정수 n이 주어졌을 때, n을 1, 2, 3의 합으로 나타내는 방법의 수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스는 한 줄로 이루어져 있고, 정수 n이 주어진다. n은 양수이며 11보다 작다.

출력

각 테스트 케이스마다, n을 1, 2, 3의 합으로 나타내는 방법의 수를 출력한다.

풀이 (동적 프로그래밍)

시간 복잡도 O(n) 공간 복잡도 O(n)

시작: 가능한 경우의 수를 저장하는 dp[] 공간 만들기, dp[0] 의 경우는 0을 만드는 경우는 1임으로 저장  

정수 n이 1, 2, 3 으로 만들어지는 경우의 수는 각각 다음과 같다 

dp[n] += dp[n-1]  

dp[n] += dp[n-2]  

dp[n] += dp[n-3]   

why?: n을 만들기 위한 방법은 (n-정수)를 만드는 방법이기 때문이다

1을 활용해서 4를 만드는 방법은 3에서 1을 더하면 4가 되기 때문에 즉 3을 만드는 방법의 개수라고 볼 수 있다.

마찬 가지로 2, 3 또한 각각 2에서 2를 더하면 4가 되고 1에서 3을 더하면 4가 되기 때문에 위와 같은 점화식이 나오게 된다.

dp[0] =1 로 채워졌기 때문에 바텀 업 방식으로 쌓아올라가면 해결이 가능하다.

import java.io.*;
import java.util.*;

public class Main {
    public static int sti(StringTokenizer st){
        return Integer.parseInt(st.nextToken());
    }
    public static long stl(StringTokenizer st){
        return Long.parseLong(st.nextToken());
    }
    public static void main(String[] args)throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        int[] dp = new int[11];
        dp[0] = 1;
        for (int i = 1; i <= 10; i++) {
            dp[i] += dp[i-1];
            if(i-2>=0) {
                dp[i] += dp[i - 2];
            }
            if(i-3>=0) {
                dp[i] += dp[i - 3];
            }
        }
        for (int i = 0; i < n; i++) {
            int num = Integer.parseInt(br.readLine());
            System.out.println(dp[num]);
        }
    }
}

'PS > Silver' 카테고리의 다른 글

[Silver II] 섬의 개수 - 4963 (Java)  (0) 2023.08.05
[Silver I] 숨바꼭질 - 1697 (Java)  (0) 2023.08.03
[Silver III] 계단 오르기 - 2579  (0) 2023.07.19
[Silver III] 1로 만들기 - 1463 (Java)  (0) 2023.07.16
[Silver IV] 설탕 배달 - 2839  (0) 2023.07.16

문제 링크

분류

다이나믹 프로그래밍

문제 설명

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

<그림 1>

예를 들어 <그림 2>와 같이 시작점에서부터 첫 번째, 두 번째, 네 번째, 여섯 번째 계단을 밟아 도착점에 도달하면 총 점수는 10 + 20 + 25 + 20 = 75점이 된다.

 

<그림 2>

계단 오르는 데는 다음과 같은 규칙이 있다.

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

따라서 첫 번째 계단을 밟고 이어 두 번째 계단이나, 세 번째 계단으로 오를 수 있다. 하지만, 첫 번째 계단을 밟고 이어 네 번째 계단으로 올라가거나, 첫 번째, 두 번째, 세 번째 계단을 연속해서 모두 밟을 수는 없다.

각 계단에 쓰여 있는 점수가 주어질 때 이 게임에서 얻을 수 있는 총 점수의 최댓값을 구하는 프로그램을 작성하시오.

입력

입력의 첫째 줄에 계단의 개수가 주어진다.

둘째 줄부터 한 줄에 하나씩 제일 아래에 놓인 계단부터 순서대로 각 계단에 쓰여 있는 점수가 주어진다. 계단의 개수는 300이하의 자연수이고, 계단에 쓰여 있는 점수는 10,000이하의 자연수이다.

출력

첫째 줄에 계단 오르기 게임에서 얻을 수 있는 총 점수의 최댓값을 출력한다.

풀이 코드 (DP)

메모리제이션 개념을 사용하여 1~2번 연속적으로 올라가서 n번째 계단에 도달하였을 때의 수의 합계를 저장하고 바텀 업 방식으로 최종 목적지 까지 도착하였을 때 그 전에 1~2번 연속적으로 올라갔을 때의 값을 비교하여 큰 값을 출력

import java.io.*;
import java.util.*;

public class Main {
    public static class Pos{
        int start;
        int end;
        public Pos(int x,int y){
            this.start = x;
            this.end = y;
        }
    }
    public static int sti(StringTokenizer st) {
        return Integer.parseInt(st.nextToken());
    }

    public static long stl(StringTokenizer st) {
        return Long.parseLong(st.nextToken());
    }
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        int[] arr = new int[n+1];
        for (int i = 1; i <= n; i++) {
            arr[i] = Integer.parseInt(br.readLine());
        }
        // n번째 계단에 오르기 위해 이전 계단에서 1~2번 연속으로 올랐을때의 수를 저장하기 위한 공간
        int[][] dp = new int[n+1][3];
        // 1번째 계단에 오르기 위해 1칸 올라온 상태의 값
        dp[1][1] = arr[1];
        // 1~2 연속으로 올라 온 경우 저장 작업
        for (int i = 0; i < n; i++) {
            if(i+1<=n)
                dp[i+1][2] = dp[i][1] + arr[i+1];
            if(i+2<=n)
                dp[i+2][1] = Math.max(dp[i][1],dp[i][2])+arr[i+2];
        }
        // n번째 계단으로 올라가기위해 이전에 1~2번 연속으로 올라간 경우 중 더 큰 값을 출력
        System.out.println(Math.max(dp[n][1],dp[n][2]));

    }
}

문제 링크

분류

다이나믹 프로그래밍

문제 설명

정수 X에 사용할 수 있는 연산은 다음과 같이 세 가지 이다.

  1. X가 3으로 나누어 떨어지면, 3으로 나눈다.
  2. X가 2로 나누어 떨어지면, 2로 나눈다.
  3. 1을 뺀다.

정수 N이 주어졌을 때, 위와 같은 연산 세 개를 적절히 사용해서 1을 만들려고 한다. 연산을 사용하는 횟수의 최솟값을 출력하시오.

입력

첫째 줄에 1보다 크거나 같고, 106보다 작거나 같은 정수 N이 주어진다.

출력

첫째 줄에 연산을 하는 횟수의 최솟값을 출력한다.

풀이 1: bottom-up

시간복잡도 O(n) 공간복잡도 O(n)

<아이디어>

1. 테이블 정의: dp[n] 은 n을 1로 만드는데 필요한 연산의 수 

2. 테이블 초기화: Arrays.fill(dp,1000001), dp[1] = 0, dp[2] = 1, dp[3] = 1

3. 로직: dp[i*3] = dp[i]+1, dp[i*2] = dp[i]+1, dp[i+1] = dp[i]+1

4. 결과 출력: System.out.println(dp[n]) 

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

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        if(n==1){
            System.out.println(0);
            return;
        }
        // 바텀 업

        // Dp 테이블 정의
        // dp[n] 은 n을 1로 만드는데 필요한 연산의 수
        int[] dp = new int[n+1];
        // Dp 테이블 초기화
        Arrays.fill(dp,1000001);
        dp[1] = 0; dp[2] = 1;
        if(n>=3) dp[3]=1;
        // 로직 구현
        // dp[i*n[i]] = dp[i] +1;
        for (int i = 1; i <=n; i++) {
            if(i*3<=n) dp[i*3] = Math.min(dp[i*3],dp[i]+1);
            if(i*2<=n) dp[i*2] = Math.min(dp[i*2],dp[i]+1);
            if(i+1<=n) dp[i+1] = Math.min(dp[i+1],dp[i]+1);
        }
        // 목표 출력
        System.out.println(dp[n]);
    }
}

풀이 2: Top-down

시간복잡도 O(n) 공간복잡도 O(n)

<아이디어>

1. 테이블 정의: dp[n] 은 n이 되기위해 행해진 연산의 수

2. 테이블 초기화: Arrays.fill(dp,1000001), dp[n] = 0

3. 로직: dp[i/3] = dp[i]+1, dp[i/2] = dp[i]+1, dp[i-1] = dp[i]+1

4. 결과 출력: System.out.println(dp[1]) 

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

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        if(n==1){
            System.out.println(0);
            return;
        }

        // 탑 다운
        // Dp 테이블 정의
        // dp[n]은 n이 만들어지는데 사용된 연산의 수
        int[] dp = new int[n+1];

        Arrays.fill(dp,Integer.MAX_VALUE);
        // Dp 테이블 초기화
        dp[n] = 0;

        // 로직 구현
        for (int i = n; i >=1 ; i--) {
            if(i%3==0) dp[i/3] = Math.min(dp[i/3],dp[i]+1);
            if(i%2==0) dp[i/2] = Math.min(dp[i/2],dp[i]+1);
            dp[i-1] = Math.min(dp[i-1],dp[i]+1);
        }

        // 목표 출력
        System.out.println(dp[1]);
      
    }
}

 

 

문제 링크

분류

다이나믹 프로그래밍, 그리디 알고리즘, 수학

문제 설명

상근이는 요즘 설탕공장에서 설탕을 배달하고 있다. 상근이는 지금 사탕가게에 설탕을 정확하게 N킬로그램을 배달해야 한다. 설탕공장에서 만드는 설탕은 봉지에 담겨져 있다. 봉지는 3킬로그램 봉지와 5킬로그램 봉지가 있다.

상근이는 귀찮기 때문에, 최대한 적은 봉지를 들고 가려고 한다. 예를 들어, 18킬로그램 설탕을 배달해야 할 때, 3킬로그램 봉지 6개를 가져가도 되지만, 5킬로그램 3개와 3킬로그램 1개를 배달하면, 더 적은 개수의 봉지를 배달할 수 있다.

상근이가 설탕을 정확하게 N킬로그램 배달해야 할 때, 봉지 몇 개를 가져가면 되는지 그 수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 N이 주어진다. (3 ≤ N ≤ 5000)

출력

상근이가 배달하는 봉지의 최소 개수를 출력한다. 만약, 정확하게 N킬로그램을 만들 수 없다면 -1을 출력한다.

풀이 1. 수학적 접근 

시간 복잡도 O(1) 공간 복잡도 O(1)

import java.io.*;
import java.util.*;

public class Main {
    public static int sti(StringTokenizer st){
        return Integer.parseInt(st.nextToken());
    }
    public static long stl(StringTokenizer st){
        return Long.parseLong(st.nextToken());
    }
    public static int calculate(int num){
        return num%5+num/5*3;
    }
    public static void main(String[] args)throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        if(n==1||n==2||n==4||n==7)
            System.out.println(-1);
        else if(n%5==0) System.out.println(n/5);
        else if(n%3==0){
            int num = n/3;
            System.out.println(calculate(num));
        }
        else if(n%3==1){
            int num = n/3-3;
            System.out.println(calculate(num)+2);
        }
        else{
            int num = n/3-1;
            System.out.println(calculate(num)+1);
        }
        br.close();
        bw.flush();
        bw.close();
    }
}

다음과 같이 수학적 패턴을 찾아내어 문제 해결 

풀이 2. 동적 프로그래밍

시간복잡도 O(n) 공간복잡도 O(n)

import java.io.*;
import java.util.*;

public class Main {
    public static int sti(StringTokenizer st){
        return Integer.parseInt(st.nextToken());
    }
    public static long stl(StringTokenizer st){
        return Long.parseLong(st.nextToken());
    }
    static final int INF = (int)1e9;

    public static void main(String[] args)throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int[] dp = new int[5100];
        Arrays.fill(dp, INF);
        dp[3] = 1;
        dp[5] = 1;
        for (int i = 3; i <= n; i++) {
            if(i+3 < 5100) dp[i+3] = Math.min(dp[i+3], dp[i]+1);
            if(i+5 < 5100) dp[i+5] = Math.min(dp[i+5], dp[i]+1);
        }
        System.out.println(dp[n] == INF ? -1 : dp[n]);

        br.close();
        bw.flush();
        bw.close();
    }
}

최대 입력값의 수 만큼 배열의 크기를 잡고 3, 5인 경우 1을 채워 두고 반복문을 통해 모든 경우의 수를 채워서 해결

+ Recent posts