문제

https://school.programmers.co.kr/learn/courses/30/lessons/42579?language=java

풀이

  1. 속한 노래가 많이 재생된 장르를 먼저 수록합니다.
  2. 장르 내에서 많이 재생된 노래를 먼저 수록합니다.
  3. 장르 내에서 재생 횟수가 같은 노래 중에서는 고유 번호가 낮은 노래를 먼저 수록합니다.

• 장르에 속한 곡이 하나라면, 하나의 곡만 선택합니다.

이 조건들을 위주로 생각하여 문제를 풀었다.

우선, 장르, 재생횟수, 인덱스를 저장하기 위해 Triple이라는 자료구조를 하나 만들어줬다.

또한, 장르별 재생횟수를 저장하기 위해 Pair 자료구조와 함께 HashMap을 사용했다.

Triple 자료구조를 저장하는 list에 모든 데이터를 담고, 이 후 재생횟수 순서대로 Triple을 정렬시켜줬다.

이후 이중 for문을 통해 장르별 최대 두개의 곡을 선택한다. 횟수가 2회가 되면 반복문을 종료하게 했다. 이로써 장르에 곡이 1개인 경우 해당 인덱스를 하나만 추가하게 된다.

list 자료구조에 담은 데이터를 배열로 변환하여 리턴하면 끝

코드

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;

class Solution {
         class Pair<T,V> {
        T first;
        V second;

        public Pair(T first, V second) {
            this.first = first;
            this.second = second;
        }
    }
    class Triple<T,K,V> {
        T first;
        K second;
        V third;

        public Triple(T first, K second, V third) {
            this.first = first;
            this.second = second;
            this.third = third;
        }
    }
    public int[] solution(String[] genres, int[] plays) {
        int[] answer = {};
        HashMap<String, Integer> map = new HashMap<>();
        ArrayList<Triple<Integer, String, Integer>> list = new ArrayList<>();
        ArrayList<Pair<String, Integer>> arrayList = new ArrayList<>();
        ArrayList<Integer> ans = new ArrayList<>();
        for (int i = 0; i < genres.length; i++) {
            map.put(genres[i], map.getOrDefault(genres[i], 0) + plays[i]);
            list.add(new Triple(i, genres[i], plays[i]));
        }
        map.forEach((s, i) -> {
            arrayList.add(new Pair(s,i));
        });
        Collections.sort(list, new Comparator<Triple<Integer, String, Integer>>() {
            @Override
            public int compare(Triple<Integer, String, Integer> o1,
                Triple<Integer, String, Integer> o2) {
                return o2.third - o1.third;
            }
        });

        Collections.sort(arrayList, new Comparator<Pair<String, Integer>>() {
            @Override
            public int compare(Pair<String, Integer> o1, Pair<String, Integer> o2) {
                return o2.second-o1.second;
            }
        });
        int idx = 0;
        for (int i = 0; i < arrayList.size(); i++) {
            String s = arrayList.get(i).first;
            int cnt = 0;
            for (int j = 0; j < list.size(); j++) {
                if (cnt == 2) break;
                if (list.get(j).second.equals(s)) {
                    ans.add(list.get(j).first);
                    cnt++;
                }
            }
        }
        answer = new int[ans.size()];
        for (int i = 0; i < ans.size(); i++) {
            answer[i] = ans.get(i);
        }
        return answer;
    }
}

문제

https://school.programmers.co.kr/learn/courses/30/lessons/1844?language=java

풀이

틀린 풀이

DFS를 통해 문제를 먼저 풀었지만 효율성 테스트에서 모두 실패했다.

새로운 풀이

문제점

DFS는 다음과 같은 문제가 있기 때문에 이 문제를 해결하는 방법으로 적합하지 않다.

  • 해가 없는 경로가 깊을 경우 탐색시간이 오래 걸릴 수 있다.
  • 얻어진 해가 최단 경로가 된다는 보장이 없다.

따라서, BFS로 다시 문제를 풀었다.

또한 맨날 헷갈리는 부분이 다익스트라(PQ)를 사용할 것인지 인데, PQ의 경우 가중치가 다른 경우에 사용한다는 것을 다시 기억하자.

일반적인 방법의 BFS를 통해 문제를 해결할 수 있다.

코드

package com.company.programmers;

import java.util.LinkedList;
import java.util.Queue;

public class P4 {
    static int[] dy = new int[] {-1,1,0,0};
    static int[] dx = new int[] {0,0,-1,1};
    static int[][] map;
    static boolean[][] visited;
    static int min = Integer.MAX_VALUE;
    public int solution(int[][] maps) {
        map = maps;

        visited = new boolean[maps.length][maps[0].length];
        visited[0][0] = true;
        bfs();
        if (min == Integer.MAX_VALUE) min = -1;
        return min;
    }
    public static void bfs() {
        Queue<int[]> queue = new LinkedList<>();
        queue.add(new int[]{0,0,1});
        while (!queue.isEmpty()) {
            int[] val = queue.poll();

            if (val[0] == map.length - 1  && val[1] == map[0].length - 1) {
                min = Math.min(val[2], min);
                return;
            }
            for (int i = 0; i < 4; i++) {
                int ny = val[0] + dy[i];
                int nx = val[1] + dx[i];

                if (ny < 0 || nx < 0 || ny >= map.length || nx >= map[0].length) continue;
                if (visited[ny][nx]) continue;
                if (map[ny][nx] == 0) continue;
                visited[ny][nx] = true;
                queue.add(new int[] {ny,nx,val[2]+1});
            }
        }

    }
    public static void main(String[] args) {
        P4 p4 = new P4();
        p4.solution(new int[][] {{1,0,1,1,1}, {1,0,1,0,1}, {1,0,1,1,1}, {1,1,1,0,1}, {0,0,0,0,1}});

    }

}

문제

https://school.programmers.co.kr/learn/courses/30/lessons/72411?language=java

풀이

소요시간 60분

틀린 풀이

조합을 통해 가능한 전체 코스의 경우의 수를 구하고, 코스가 등장한 갯수를 구하기 위해 map을 이용했다. 이후 map에서 2개 이상 등장 했으며, 코스의 길이가 orders 배열에 포함된 경우 추가하도록 했다.

문제점

코스에 만약 ABCD가 있다면, BCD, CD 와같이 ABCD 내에 포함된 경우를 제거하는 방법을 찾지 못해 풀지 못했다.

새로운 풀이

2개의 코스 요리중 가장 많이 주문한 경우, 3개의 코스 요리중 가장 많이 주문한 경우와 같이 각 코스종류마다 답을 구하도록 수정했다. 풀이를 보고나서 다시 문제를 보니, 이 부분이 중요했다는 사실을 알 수 있었다.

또한, 3번째 입출력 예의 orders 입력을 보면 “WXA”와 같이 정렬되지 않은 상태로 들어오는 데이터가 있는데, 이는 조합을 구할때 문제가 되므로 정렬해주자

코드

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map.Entry;

class Solution {

    static HashMap<String, Integer> map = new HashMap<>();
    public String[] solution(String[] orders, int[] course) {
        ArrayList<String> arr = new ArrayList<>();
        String[] answer = {};
        for(int i =0;i<orders.length;i++){
            // 2. 각 문자열을 문자형 배열로 변환.
            char[] charArr = orders[i].toCharArray();
            // 3. 해당 문자형 배열을 정렬.
            Arrays.sort(charArr);
            // 4. 정렬된 문자형 배열을 문자열로 변환해 저장.
            orders[i] = String.valueOf(charArr);
        }
        for (int i = 0; i < course.length; i++) {
            map.clear();
            int max = Integer.MIN_VALUE;
            for (int j = 0; j < orders.length; j++) {
                if (course[i] <= orders[j].length()) {
                    String s = orders[j];
                    String res = "";
                    combi(s, res, 0, 0, course[i]);
                }
            }
            for(Entry<String,Integer> entry : map.entrySet()){
                max = Math.max(max,entry.getValue());
            }
            for(Entry<String,Integer> entry : map.entrySet()){
                if(max >=2 && entry.getValue() == max)
                    arr.add(entry.getKey());
            }
        }
        Collections.sort(arr);
        
        return arr.toArray(answer);
    }

    public static void combi(String origin, String result, int idx, int cnt, int n) {
        if (cnt == n) {
            map.put(result, map.getOrDefault(result, 0) + 1);
            return;
        }
        for (int i = idx; i < origin.length(); i++) {
                result += origin.charAt(i);
                combi(origin, result, i+1, cnt+1, n);
                result = result.substring(0, result.length()-1);
            }
    }
}

개요

vue project 개발 중 로컬에서 앱 시작 시 오류 발생하며, 처음 페이지 로딩 시 콘솔에 아래의 로그가 표시됨.

GET <http://localhost:3000/@id/__x00__plugin-vuetify:components/VDivider/VDivider.sass> net::ERR_ABORTED 404 (Not Found)
chunk-GGIT7KZK.js:46 
        
GET <http://localhost:3000/@id/__x00__plugin-vuetify:components/VImg/VImg.sass> net::ERR_ABORTED 404 (Not Found)
chunk-GGIT7KZK.js:49 
        
GET <http://localhost:3000/@id/__x00__plugin-vuetify:components/VResponsive/VResponsive.sass> net::ERR_ABORTED 404 (Not Found)

오류 환경

OS : Windows 10 Enterprise

Vue 3.3.0 + Vuetify 3.0 + typescript

npm run dev 실행

오류 내용 분석

로그를 읽어보면 vuetify의 sass 파일을 찾지 못해 페이지를 보여주지 못하는 것으로 보인다. 관련 내용으로 구글링 해보자.

오류 확인

관련 이슈를 vuetify 공식 github 페이지에서 찾았다.

https://github.com/vuetifyjs/vuetify/issues/7950

나의 경우 위 포스팅의 아래 댓글을 확인하여 해결 했다.

  1. node_modules 제거rm -r .\\node_modules\\
  2. package-lock.json 제거rm -r .\\package-lock.json
  3. "sass": "1.32.13"
  4. devDependency 섹션 아래 package.json에 한 줄을 추가합니다 .

오류 수정

내 코드의 경우 프로젝트 생성 시 아래처럼 1.69.0 버전으로 추가 되었다.

이를 1.32.13으로 수정해주니 더이상 문제가 발생하지 않았다.

버전 충돌로 인하여 발생한 문제 같다.

문제

https://school.programmers.co.kr/learn/courses/30/lessons/250134?language=java

풀이

처음 실패한 풀이

빨간 수레와 파란 수레를 각각 다르게 DFS를 수행하고 이동 여부를 체크하여 따로 수레를 이동시키게끔 구현 했으나, 턴 카운팅 하는 과정에서 어려움을 느껴 포기했다.

이후 다른 풀이를 보고, 빨간 수레와 파란 수레 각각 4개의 경우로 이동 시키므로 4X4개의 경우로 한번에 이동시키게끔 하는 풀이로 수정 했다.

또한, 각각의 수레마다 이동한 위치로 다시 돌아갈 수 없으며, 서로 이동한 위치로는 이동이 가능하다. 따라서 이를 위해 각각 visited 배열을 활용했다.

코드

class Solution {
     static int EMPTY_SPACE = 0;
    static int RED_START = 1;
    static int BLUE_START = 2;
    static int RED_END = 3;
    static int BLUE_END = 4;
    static int WALL = 5;
    static int[] dx = new int[] {-1,1,0,0};
    static int[] dy = new int[] {0,0,-1,1};
    static int[] originRedPos = new int[2];
    static int[] originBluePos = new int[2];
    static int[] redEndPos = new int[2];
    static int[] blueEndPos = new int[2];
    static int count = Integer.MAX_VALUE;
    static boolean[][] redVisited;
    static boolean[][] blueVisited;
    static int[][] arr;
    public int solution(int[][] maze) {
        int answer = 0;

        arr = maze;
        // red 먼저 시작, blue 먼저 시작
        for (int i = 0; i < maze.length; i++) {
            for (int j = 0; j < maze[i].length; j++) {
                if (maze[i][j] == RED_START) {
                    originRedPos[0] = i;
                    originRedPos[1] = j;
                } else if (maze[i][j] == BLUE_START) {
                    originBluePos[0] = i;
                    originBluePos[1] = j;
                } else if (maze[i][j] == RED_END) {
                    redEndPos[0] = i;
                    redEndPos[1] = j;
                } else if (maze[i][j] == BLUE_END) {
                    blueEndPos[0] = i;
                    blueEndPos[1] = j;
                }
            }
        }
        redVisited = new boolean[maze.length][maze[0].length];
        blueVisited = new boolean[maze.length][maze[0].length];
        redVisited[originRedPos[0]][originRedPos[1]] = true;
        blueVisited[originBluePos[0]][originBluePos[1]] = true;
        dfs(originRedPos[1], originRedPos[0], originBluePos[1], originBluePos[0], 0);
        if (count == Integer.MAX_VALUE) count = 0;
        return count;
    }

 
        public static void dfs(int redX, int redY, int blueX, int blueY, int round) {
        if (redY == redEndPos[0] && redX == redEndPos[1] && blueY == blueEndPos[0] && blueX == blueEndPos[1]) {
            count = Math.min(round, count);
            return;
        }
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                int[] newRedPos = new int[2];
                int[] newBluePos = new int[2];
                boolean skipRedCheck = false;
                boolean skipBlueCheck = false;
                // 새로운 이동 지점 선택
                if (redY == redEndPos[0] && redX == redEndPos[1]) {
                    newRedPos[0] = redY;
                    newRedPos[1] = redX;
                    skipRedCheck = true;
                } else {
                    newRedPos[0] = redY + dy[i];
                    newRedPos[1] = redX + dx[i];
                }
                if (blueY == blueEndPos[0] && blueX == blueEndPos[1]) {
                    newBluePos[0] = blueY;
                    newBluePos[1] = blueX;
                    skipBlueCheck = true;
                } else {
                    newBluePos[0] = blueY + dy[j];
                    newBluePos[1] = blueX + dx[j];
                }

                 // 이동 가능 여부 체크
                if (newRedPos[0] < 0 || newRedPos[1] < 0 || newBluePos[0] < 0 || newBluePos[1] < 0
                || newRedPos[0] >= arr.length || newRedPos[1] >= arr[0].length || newBluePos[0] >= arr.length || newBluePos[1] >= arr[0].length) continue;

                if (arr[newRedPos[0]][newRedPos[1]] == WALL || arr[newBluePos[0]][newBluePos[1]] == WALL) continue;

                // 이미 도착한 경우 움직이지 않게되므로 중복체크 하지 않음
                if (!skipRedCheck) {
                    if (redVisited[newRedPos[0]][newRedPos[1]]) continue;
                }
                if (!skipBlueCheck) {
                    if (blueVisited[newBluePos[0]][newBluePos[1]]) continue;
                }

                // 수레가 같은 곳으로 이동하려는 경우
                if (newRedPos[0] == newBluePos[0] && newRedPos[1] == newBluePos[1]) continue;
                if (newRedPos[0] == blueY && newRedPos[1] == blueX && newBluePos[0] == redY && newBluePos[1] == redX) continue;
                // 이동 및 round 추가
                redVisited[newRedPos[0]][newRedPos[1]] = true;
                blueVisited[newBluePos[0]][newBluePos[1]] = true;
                dfs(newRedPos[1], newRedPos[0], newBluePos[1], newBluePos[0], round + 1);
                redVisited[newRedPos[0]][newRedPos[1]] = false;
                blueVisited[newBluePos[0]][newBluePos[1]] = false;
            }
        }
    }
}

문제

시간 제한 메모리 제한 제출 정답 맞힌 사람 정답 비율

2 초 128 MB 2792 1210 897 47.335%

문제

지민이는 카지노의 딜러이고, 지금 3명의 플레이어(0, 1, 2)가 있다. 이 게임은 N개의 카드를 이용한다. (0 ~ N-1번)

일단 지민이는 카드를 몇 번 섞은 다음에, 그것을 플레이어들에게 나누어 준다. 0번째 위치에 있던 카드가 플레이어 0에게 가고, 1번째 위치에 있던 카드는 플레이어 1에게 가고, 2번째 위치에 있던 카드는 플레이어 2에게 가고, 3번째 위치에 있던 카드는 플레이어 0에게 가고, 이런식으로 카드를 나누어 준다. 하지만, 지민이는 약간 사기를 치려고 한다.

지민이는 처음에 카드를 섞기 전에 카드의 순서를 알고 있고, 이 정보를 이용해 각 카드가 특정한 플레이어에게 보내지게 할 것이다.

카드를 한 번 섞을 때는 주어진 방법을 이용해서만 섞을 수 있고, 이 방법은 길이가 N인 수열 S로 주어진다. 카드를 한 번 섞고 나면 i번째 위치에 있던 카드는 S[i]번째 위치로 이동하게 된다.

각 카드가 어떤 플레이어에게 가야 하는지에 대한 정보는 길이가 N인 수열 P로 주어진다. 맨 처음 i번째 위치에 있던 카드를 최종적으로 플레이어 P[i] 에게 보내야한다.

지민이가 목적을 달성하기 위해 필요한 카드 섞는 횟수의 최솟값을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 N이 주어진다. N은 3보다 크거나 같고, 48보다 작거나 같은 3의 배수이다.

둘째 줄에 길이가 N인 수열 P가 주어진다. 수열 P에 있는 수는 0, 1, 2 중 하나이다.

셋째 줄에 길이가 N인 수열 S가 주어진다. 수열 S에 있는 수는 모두 N-1보다 작거나 같은 자연수 또는 0이고 중복되지 않는다.

출력

첫째 줄에 몇 번 섞어야 하는지 출력한다. 만약, 섞어도 섞어도 카드를 해당하는 플레이어에게 줄 수 없다면, -1을 출력한다.

풀이

가장 중요한 것은 섞어도 카드를 해당하는 플레이어에게 줄 수 없는 경우를 구하는 것인데, 나 같은 경우 섞다가 맨 처음 형태와 같은 형태가 나온다면 (싸이클이 있다면) 원하는 플레이어 에게 줄 수 없다고 생각했다.

따라서 아래와 같이 반복 계산 해줬다.

  1. 한번이라도 섞고 난 이후에 처음 형태와 같아졌을 경우 반복을 종료하고 -1 리턴
  2. 0,1,2 순서대로 잘 섞여졌다면 반복을 종료
  3. 카드 섞기
  4. 섞은 수 올려주기

문제를 제대로 못 읽어서 N, S를 거꾸로 생각하는 바람에 많은 시간을 사용했다.

코드

package com.company.baekjoon;

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

public class B1091 {
    static int N;
    static int[] P;
    static int[] S;
    static int[] origin;
    static int[] tmp;
    static int cnt;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken());

        P = new int[N];
        origin = new int[N];
        tmp = new int[N];
        S = new int[N];

        st = new StringTokenizer(br.readLine());
        for (int i = 0; i < N; i++) {
            P[i] = Integer.parseInt(st.nextToken());
            origin[i] = P[i];
        }

        st = new StringTokenizer(br.readLine());
        for (int i = 0; i < N; i++) {
            S[i] = Integer.parseInt(st.nextToken());
        }
        
        while (true) {
            if (cnt > 0 && isEqual()) {
                cnt = -1;
                break;
            }
            if (isStop()) {
                break;
            }
            swap();
            cnt++;
        }
        System.out.println(cnt);
    }
    public static boolean isEqual() {
        for (int i = 0; i < N; i++) {
            if (tmp[i] != origin[i]) return false;
        }
        return true;
    }
    public static boolean isStop() {
        for (int i = 0; i < N; i++) {
            if (P[i] != i % 3) return false;
        }
        return true;
    }
    public static void swap() {
        for (int i = 0; i < N; i++) {
            tmp[S[i]] = P[i];
        }
        P = Arrays.copyOf(tmp, tmp.length);
    }
}

문제

시간 제한 메모리 제한 제출 정답 맞힌 사람 정답 비율

2 초 128 MB 1515 457 365 33.610%

문제

민식이는 다음과 같이 두 개의 명령만 지원하는 새로운 텍스트 에디터를 만들었다.

  • “type c" : 현재 글의 가장 뒤에 문자 c를 추가한다.
  • “undo t" : 이전 t초동안 수행된 작업을 역순으로 되돌린다.

처음 텍스트 에디터는 비어있다.

예를 들어, 다음과 같은 명령을 진행했다고 하자.

  • 1초 : type a
  • 2초 : type b
  • 3초 : type c
  • 5초 : undo 3

3초가 끝날 때, 텍스트는 "abc"이다. 5초때, 이전 3초동안 한 작업을 역순으로 되돌려야 한다. c는 지워지고, b도 지워질 것이다. 따라서 a만 남는다.

되돌리기가 되돌리기 될 수도 있다.

예를 들어

  • 1초 : type a
  • 2초 : type b
  • 3초 : undo 2
  • 4초 : undo 2

2초일 때, 텍스트는 “ab"이다. 3초때 모든 것이 되돌리기 되므로 텍스트는 빈 텍스트가 되고, 4초때 3초때 되돌리기 한 것이 되돌리기 되고, 2초때 type b한 것이 지워진다. 따라서 텍스트는 ”a"가 된다.

명령과 수행한 시간이 주어질 때, 마지막에 남은 텍스트를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 명령의 개수 N이 주어진다. 둘째 줄부터 N개의 줄에 명령과 수행한 시간이 주어진다. 항상 시간이 오름차순으로 주어지고, type c에서 c는 알파벳 소문자, undo t에서 t는 1보다 크거나 같고 109보다 작거나 같은 자연수이다. N은 50보다 작거나 같은 자연수이다.

출력

첫째 줄에 모든 명령을 수행한 후에 남아있는 텍스트를 출력한다.

풀이

아이디어를 떠올리는것이 굉장히 어려워 해답을 봤음.

처음엔 스택을 통해 undo 시 해당 로그를 저장하고 복구하는 형식으로 구현하려고 했는데, undo가 중첩되었을 때 구현을 어떻게 해야할지 잘 떠오르지 않았다.

그러나 해답을 보던 중 아래의 방법을 사용하여 다시 풀어봤다.

각 명령이 들어올때마다 벡터에 해당 시간과 문자를 저장하고 undo 명령시 (현재 시간 - t)초 이전의 상태를 불러오면 됩니다.

type 명령의 경우, 리스트의 가장 끝에 있는 데이터에 추가로 문자를 더해준 후 리스트에 추가한다.

undo 명령의 경우 리스트의 끝부터 순회하며, 리스트의 아이템의 시간보다 입력된 시간 - undo 시간이 크다면 해당 문자열을 리스트에 추가한다.(undo가 적용된 문자열을 리턴)

되돌리기라는 이름을 듣고 바로 스택을 써야한다고만 생각했는데, 문제를 보는 시각을 더 늘리자..

코드

package com.company.baekjoon;

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

public class B1360 {

    static ArrayList<Pair> list = new ArrayList<>();

    public static class Pair {
        int first;
        String second;
        public Pair(int first, String second) {
            this.first = first;
            this.second = second;
        }
    }
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        int N = Integer.parseInt(st.nextToken());

        list.add(new Pair(0, ""));
        for (int i = 0; i < N; i++) {
            st = new StringTokenizer(br.readLine());

            String prompt = st.nextToken();

            if (prompt.equals("type")) {
                String character = st.nextToken();
                int time = Integer.parseInt(st.nextToken());
                list.add(new Pair(time, list.get(list.size()-1).second + character));
            } else {
                int undo = Integer.parseInt(st.nextToken());
                int time = Integer.parseInt(st.nextToken());
                list.add(new Pair(time, getStr(time-undo)));
            }
        }
        System.out.println(list.get(list.size()-1).second);

    }
    public static String getStr(int second) {
        for (int i = list.size()-1; i >= 0; i--) {
            if (list.get(i).first < second) {
                return list.get(i).second;
            }
        }
        return "";
    }
}

문제

시간 제한 메모리 제한 제출 정답 맞힌 사람 정답 비율

2 초 128 MB 7011 3924 3058 55.109%

문제

한수는 캠프를 마치고 집에 돌아가려 한다. 한수는 현재 왼쪽 아래점에 있고 집은 오른쪽 위에 있다. 그리고 한수는 집에 돌아가는 방법이 다양하다. 단, 한수는 똑똑하여 한번 지나친 곳을 다시 방문하지는 않는다.

      cdef  ...f  ..ef  ..gh  cdeh  cdej  ...f
      bT..  .T.e  .Td.  .Tfe  bTfg  bTfi  .Tde
      a...  abcd  abc.  abcd  a...  a.gh  abc.
거리 :  6     6     6     8     8    10    6

위 예제는 한수가 집에 돌아갈 수 있는 모든 경우를 나타낸 것이다. T로 표시된 부분은 가지 못하는 부분이다. 문제는 R x C 맵에 못가는 부분이 주어지고 거리 K가 주어지면 한수가 집까지도 도착하는 경우 중 거리가 K인 가짓수를 구하는 것이다.

입력

첫 줄에 정수 R(1 ≤ R ≤ 5), C(1 ≤ C ≤ 5), K(1 ≤ K ≤ R×C)가 공백으로 구분되어 주어진다. 두 번째부터 R+1번째 줄까지는 R×C 맵의 정보를 나타내는 '.'과 'T'로 구성된 길이가 C인 문자열이 주어진다.

출력

첫 줄에 거리가 K인 가짓수를 출력한다.

풀이

R,C의 범위가 매우 작으므로 DFS를 활용하여 문제를 풀었다.

왼쪽 아래 (r-1, 0) 부터 오른쪽 위 (0, c-1) 까지 이동하는 경우 중 k번의 이동으로 도달 가능한 갯수를 작성하는 문제이므로 (r-1, 0) 부터 (0, c-1) 까지 depth를 늘려가며 1칸씩 이동하도록 해줬다. 별다른 어려움은 없이 풀었다.

코드

package com.company.baekjoon;

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

public class B1189 {
    static int[] dx = new int[] {-1,1,0,0};
    static int[] dy = new int[] {0,0,-1,1};
    static int r;
    static int c;
    static int k;
    static int[][] arr;
    static boolean[][] visited;

    static int count;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        r = Integer.parseInt(st.nextToken());
        c = Integer.parseInt(st.nextToken());
        k = Integer.parseInt(st.nextToken());

        arr = new int[r][c];
        visited = new boolean[r][c];

        for (int i = 0; i < r; i++) {
            st = new StringTokenizer(br.readLine());
            String s = st.nextToken();
            for (int j = 0; j < s.length(); j++) {
                if (s.charAt(j) == '.') {
                    arr[i][j] = 0;
                } else {
                    arr[i][j] = 1;
                }
            }
        }
        visited[r-1][0] = true;
        dfs(r-1, 0, 1);
        System.out.println(count);
    }
    public static void dfs(int i, int j, int depth) {
        if (i == 0 && j == c-1) {
            if (depth == k) count++;
            return;
        }
        for (int l = 0; l < dy.length; l++) {
            int ny = i + dy[l];
            int nx = j + dx[l];

            if (ny < 0 || nx < 0 || ny >= r || nx >= c) continue;
            if (visited[ny][nx]) continue;
            if (arr[ny][nx] == 1) continue;
            visited[ny][nx] = true;
            dfs(ny, nx, depth + 1);
            visited[ny][nx] = false;
        }
    }
}

개요

SSAFY 계절학기 강의 수강 중 싱글스레드에서 비동기 요청을 할 수 있다는 내용이 있었는데, 필자가 개발하면서 싱글 스레드 환경에서 비동기 요청을 한 기억이 없어서 그런지 잘 이해가 되지 않았다. 이를 이해하고자 문서를 작성해본다.

자바스크립트에서 싱글 스레드와 비동기 요청

아래와 같이 “싱글스레드 비동기” 라는 키워드로 구글에 검색을 해봤다. 그러나 이 부분은 자바스크립트 언어가 어떻게 싱글 스레드에서 비동기 요청 처리를 가능하게 하는 것인지 말해주는 내용이 대다수 였다. 글들을 읽어보면 결국에는 자바스크립트 언어 자체는 싱글 스레드로 동작하는 것이고, 비동기 요청의 경우 Browser API를 통해 비동기 요청을 처리 후 콜백 처리를 해주는 것으로 이해했다.

따라서, 자바스크립트도 결국에는 완전한 싱글 스레드가 아니라고 생각했다.

그렇다면 완전한 싱글스레드 환경에서 비동기 요청은 가능한 것일까?

알고리즘을 풀 때 처럼 main 함수만 있는 환경에서도 비동기 요청이 가능할 지 궁금했다.

대표적인 비동기 요청 사례인 File I/O를 통해 이를 알아보자.

동기 및 비동기 I/O

동기 파일 I/O에서 스레드는 I/O 작업을 시작하고 I/O 요청이 완료될 때까지 즉시 대기 상태로 돌입합니다. 비동기 파일 I/O를 수행하는 스레드는 적절한 함수를 호출하여 커널에 I/O 요청을 보냅니다. 커널에서 요청을 수락하는 경우, 커널이 스레드에 I/O 작업이 완료되었다는 신호를 보낼 때까지 호출 스레드가 다른 작업을 계속 처리합니다. 그런 다음 현재 작업을 중단하고 필요에 따라 I/O 작업에서 데이터를 처리합니다.

 

아래는 동기 I/O와 비동기 I/O를 그림으로 표현한 것이다.

 

간단하게 정리해보면, I/O 처리의 경우 커널모드 스레드에서 수행된다. 싱글 스레드라고 믿었던 프로그램도 사실은 커널모드 스레드를 통해 I/O 작업을 수행하고 있다. 따라서 위와 같은 파일 I/O 작업의 경우 싱글스레드 프로그램에서도 비동기 요청이 가능하다. 다만, 파일 I/O의 경우 커널모드의 스레드를 이용하므로, 이와 같이 타 스레드에 작업을 위임하지 않는 이상 싱글 스레드에서 비동기 요청은 불가능하다고 생각한다.

싱글 스레드에서 비동기, 효율적인가?

서비스를 개발하면서 싱글 스레드에서 비동기 요청을 하는 경우를 많이 이용해본 적이 없다. 왜일까? 만약, 파일 읽기 요청과 다른 작업을 함께 수행한다고 생각해보면 굉장히 불편할 것이라고 생각한다. 다른 작업을 수행하면서 수시로 커널에 I/O가 완료 되었는지 물어봐야 할 것이고, 에러처리도 어려울 것이다.

비동기 처리 아키텍쳐

Spring, Android와 같은 많은 프레임워크에 비동기 처리를 지원하는 API를 포함하거나, 라이브러리 형태로 사용하기 좋게끔 만들어져 있다. 이러한 라이브러리들은 어떤 구조로 만들어져 있을까? 대표적인 비동기 요청 라이브러리인 Spring WebClient를 통해 알아보자.

Spring WebFlux includes a client to perform HTTP requests with. WebClient has a functional, 
fluent API based on Reactor, see Reactive Libraries, which enables declarative composition 
of asynchronous logic without the need to deal with threads or concurrency. It is fully 
non-blocking, it supports streaming, and relies on the same codecs that are also used to 
encode and decode request and response content on the server side.

Spring WebFlux에 내장된 WebClient의 경우 netty를 기반으로 만들어졌다고 한다. netty는 Reactor Pattern으로 만들어졌다고 하는데, Reactor Pattern에 대해서도 간단히 알아보자.

Reactor Pattern

하나 이상의 클라이언트로부터의 요청을 동시 처리하기 위해서 사용하는 패턴이다.

어플리케이션에서 이벤트를 처리하기 위한 루프를 도는 것이 아니라, 이벤트에 반응하는 객체(reactor)를 만들고, 이벤트가 발생하면 어플리케이션 대신 reactor가 반응하여 처리하는 것이다.

정리

이와 같이 싱글 스레드에서 비동기 요청을 할 수는 있지만, 사용되지 않는 이유가 있는 것 같다. 결론을 내보자면 아래와 같다.

  • 싱글 스레드에서 일부 작업의 경우 비동기 요청이 가능하다.
  • Reactor Pattern이 등장 한 것처럼 비동기 요청의 경우 멀티 스레드를 이용하여 구현하는 것이 효율적일 것이다.

Reference

동기 및 비동기 I/O - Win32 apps

WebClient :: Spring Framework

리액터패턴 / 프로액터패턴

 

문제

https://school.programmers.co.kr/learn/courses/30/lessons/42627

풀이

우선순위 큐를 활용하여 문제를 풀었음. (정렬이 필요하므로)

 

정렬

  1. 작업큐
    1. 작업큐의 경우 기본적으로 작업이 들어온 순서대로 정렬되어야 한다. 또한, 같은 시간에 작업이 들어온 경우, 작업 시간이 더 짧은 작업을 먼저 처리하는것이 효율적이다.
  2. 대기큐
    1. 작업 시간이 짧은 순서대로 정렬해주자.

작업 고르기

  1. 현재 대기중인 작업이 있는 경우
    1. 대기 큐에서 작업시간이 가장 짧은 작업을 고른다.
    2. 만약 현재 시간보다 작업 큐에서 꺼낸 작업의 시작시간이 더 크다면, 시간을 꺼낸 작업의 시작시간으로 변경해준다. (새로운 작업은 현재 시간보다 100초 이후에 시작될수도 있다.)
  2. 현재 대기중인 작업이 없는 경우
    1. 요청이 들어온 순서대로 작업을 고른다.

작업 실행

작업 시간만큼 반복하면서 시간을 더해주고, 현재 시간에 새로운 작업이 추가되면 작업큐에서 대기큐로 옮겨준다.

작업 완료

작업이 완료되었다면 걸린 시간을 정답에 추가해준다. 작업큐와 대기큐가 모두 비었다면, 전체 작업의 갯수만큼 나눠서 리턴해준다.

코드

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.PriorityQueue;

public class Solution {
    static int[] current;
    static int time;
        public int solution(int[][] jobs) {
        int answer = 0;
        int count = jobs.length;
        PriorityQueue<int[]> list = new PriorityQueue<int[]>(new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                if (o1[0] == o2[0]) {
                    return o1[1]-o2[1];
                }
                else return o1[0]-o2[0];
            }
        });
               PriorityQueue<int[]> wait = new PriorityQueue<int[]>(new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[1] - o2[1];
            }
        });

        for (int i = 0; i < jobs.length; i++) {
            list.add(jobs[i]);
        }

        while (true) {
            if (wait.isEmpty() && list.isEmpty()) break;
            // 맨 위에 있는거 꺼내기
            if (wait.isEmpty()) {
                current = list.poll();
                if (current[0] > time) {
                    time = current[0];
                }
            } else {
                current = wait.poll();
            }

            for (int i = 0; i < current[1]; i++) {
                if (!list.isEmpty() && list.peek()[0] <= time) {
                    wait.add(list.poll());
                }
                time++;
            }
            answer += time - current[0];
        }

 
        return answer / count;
    }

}

 

 

 

한국어 읽는게 제일 어렵다..

+ Recent posts