알고리즘 유형별 문제풀이

류호석배 1회 모의 코테

[Part3 Ch03 류호석배 코딩테스트 모의고사] 류호석배 1회 모의 코테

백준 20164번 - 홀수 홀릭 호석


출제 의도

  • 완전 탐색 접근을 통해서 모든 경우 직접 하나하나 찾아내 보자
  • 본 문제에서 “경우”란, 조건을 만족하게 매 순간 수를 잘라서 시뮬레이션 하는 것을 의미한다.
  • 수가 길이가 9자리 이기 때문에 전부 다 해보아도 경우의 수가 많지 않음을 알 수 있다.

완전 탐색 접근

  • 완전 탐색은 재귀 함수가 제 맛이다.
  • 함수 디자인을 먼저 해보자.
    1. 매 순간 들고 있는 수: x
    2. 시작부터 지금까지 얻은 점수: total_odd_cnt

static void dfs(int x,total_odd_cnt){

}

  • 재귀 함수 조건
    1. 종료 조건 : x가 한 자리 수인 경우
    2. 아닌 경우: 가능한 경우로 모두 잘라서 다음 수에 대해 재귀 호출

시간, 공간 복잡도 계산하기

수가 3개로 나눠지고 합쳐질 때마다 크기가 매우 작아지기 때문에
모든 경우의 수가 1억을 절대 넘지 않음을 알 수 있다.

구현


public class baekjoon20164 {

  static int N;
  static int max = Integer.MIN_VALUE;
  static int min = Integer.MAX_VALUE;

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

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

    N = Integer.parseInt(br.readLine());
    dfs20164(N, get_odd_cnt(N));
    System.out.println(min + " " + max);
  }

  private static void dfs20164(int n, int total_old_cnt) {

    if (n <= 9) {
      max = Math.max(max, total_old_cnt);
      min = Math.min(min, total_old_cnt);

      return;
    }

    if (n <= 99) {
      int next = (n / 10) + (n % 10);
      dfs20164(next, total_old_cnt + get_odd_cnt(next));
    }

    String s = Integer.toString(n);

    for (int i = 0; i <= s.length() - 3; i++) {
      for (int j = i + 1; j <= s.length() - 2; j++) {
        String x1 = s.substring(0, i + 1);
        String x2 = s.substring(i + 1, j + 1);
        String x3 = s.substring(j + 1);

        int next = Integer.parseInt(x1) + Integer.parseInt(x2) + Integer.parseInt(x3);

        dfs20164(next, get_odd_cnt(next) + total_old_cnt);
      }
    }

  }

  private static int get_odd_cnt(int n) {
    int res = 0;

    while (n > 0) {
      int digit = n % 10;
      if (digit % 2 != 0) res++;
      n /= 10;
    }

    return res;
  }


}


백준 20165번 - 인내의 도미노 장인 호석


출제 의도

  • 문제를 해결하는 행위 자체가 “모든 순간을 올바르게 재현하기” 즉 시뮬레이션 이다.
  • 문제에서 주어지는 상황을 올바르게 이해하고, 구현해내는 연습이 필요하다.

시뮬레이션의 핵심

  • 상태를 표현하는 방법 정하기
    • 게임판의 상태를 표현하기
  • 문제에 필요한 행위를 함수로 표현하기
    • 필요한 행위
      • 공격
        • 넘어뜨린 도미노의 위치 (x,y)
        • 특정 방향, dir 으로 도미노를 밀기
        • 밀린 도미노 높이를 이용해 연쇄적으로 밀기
      • 수비
        • 특정 위치의 상태를 복원하기

시간, 공간 복잡도 계산하기

한 번의 공격은 O(N)의 시간이
한번의 수비는 O(1)의 시간이
총 시간 복잡도는 O(R*N)이 된다.

구현


public class baekjoon20165 {

  static int N, M, R, result;
  static int[][] arr, backup;


  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());
    M = Integer.parseInt(st.nextToken());
    R = Integer.parseInt(st.nextToken());

    arr = new int[N + 1][M + 1];
    backup = new int[N + 1][M + 1];

    for (int i = 1; i <= N; i++) {
      st = new StringTokenizer(br.readLine(), " ");
      for (int j = 1; j <= M; j++) {
        arr[i][j] = Integer.parseInt(st.nextToken());
        backup[i][j] = arr[i][j];
      }
    }

    for (int i = 1; i <= R; i++) {

      st = new StringTokenizer(br.readLine(), " ");

      int x = Integer.parseInt(st.nextToken());
      int y = Integer.parseInt(st.nextToken());
      String d = st.nextToken();

      attack(x, y, d);


      st = new StringTokenizer(br.readLine(), " ");
      x = Integer.parseInt(st.nextToken());
      y = Integer.parseInt(st.nextToken());

      arr[x][y] = backup[x][y];

    }

    System.out.println(result);

    for (int i = 1; i <= N; i++) {
      for (int j = 1; j <= M; j++) {
        if (arr[i][j] == 0) {
          System.out.print("F ");
        } else {
          System.out.print("S ");
        }
      }
      System.out.println();
    }

  }

  private static void attack(int x, int y, String d) {
    if (arr[x][y] == 0) return;


    int dx = 0, dy = 0;

    switch (d) {
      case "E":
        dy = 1;
        break;
      case "W":
        dy = -1;
        break;
      case "S":
        dx = 1;
        break;
      case "N":
        dx = -1;
        break;
    }

    int cnt = arr[x][y];
    while (x >= 1 && x <= N && y >= 1 && y <= M && cnt >= 1) {
      if (arr[x][y] != 0) result++;

      cnt = Math.max(cnt - 1, arr[x][y] - 1);

      arr[x][y] = 0;

      x += dx;
      y += dy;
    }

  }

}


백준 20165번 - 문자열 지옥에 빠진 호석


출제 의도

  • 문제에 주어진 조건에 맞게 탐색하는 효율적인 구현하기
  • 이동을 통해 얻을 수 있는 모든 문자열은?
    • 시작점 결정
    • 4번까지의 이동이 가능(신이 좋아하는 문자열은 최대 5글자)
    • 매 이동마다 8가지 방향 가능

생각의 흐름 - 필요한 자료구조는?

  • 어떤 문제열이 몇번 등장할 수 있는지를 기록할 변수
    • M[문자열 S] = S의 등장 횟수
    • 모든 탐색을 미리 해서, M이라는 자료구조를 가득 채워 놓자
  • 신이 좋아하는 문자열을 입력 받을 때마다 등장 횟수 출력
    • 모든 문자열의 등장 횟수를 M에 모두 기록해버렸으니까, 신이 좋아하는 문자열을 입력 받으면 바로 출력이 가능하다
  • 그렇자면 M이라는 자료주고는 어떤 것을 지원해야 하는가?
    • M에 수행할 연산
      • 탐색: 문자열 S가 한 번 더 등장했어
      • 출력: 문자열 S는 몇 번 등장 했니?
    • HashMap<String, Integer> M을 사용하면 둘 모두 평균 O(1)

탐색 경우의 수

  • 시작 위치 * (4번의 이동, 매번 8방향 가능)
  • 1초 안에 충분히 수행이 가능하다.

구현


public class baekjoon20166 {

  static int N, M, K;
  static String[] arr;

  static Map<String, Integer> map = new HashMap<>();

  static int[][] move = { {-1, 0}, {0, 1}, {1, 0}, {0, -1}, {-1, -1}, {-1, 1}, {1, 1}, {1, -1} };

  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());
    M = Integer.parseInt(st.nextToken());
    K = Integer.parseInt(st.nextToken());

    arr = new String[N];

    for (int i = 0; i < N; i++) {
      arr[i] = br.readLine();
    }

    for (int i = 0; i < N; i++) {
      for (int j = 0; j < M; j++) {
        dfs20166(i, j, Character.toString(arr[i].charAt(j)), 1);
      }
    }


    while (K-- > 0) {
      String str = br.readLine();

      if (map.containsKey(str)) System.out.println(map.get(str));
      else System.out.println(0);

    }

  }

  private static void dfs20166(int x, int y, String path, int length) {
    if (map.containsKey(path)) map.put(path, map.get(path) + 1);
    else map.put(path, 1);

    if (length == 5) return;

    for (int i = 0; i < 8; i++) {
      int nx = (x + move[i][0]) % N;
      int ny = (y + move[i][1]) % M;
      if (nx < 0) nx += N;
      if (ny < 0) ny += M;

      dfs20166(nx, ny, path + arr[nx].charAt(ny), length + 1);
    }
  }


}


백준 20181번 - 꿈틀꿈틀 호석 애벌레


출제 의도

  • 기능성
    • 문제의 상황을 시뮬레이션으로 바꿔서 모든 가능한 경우를 수행해보기
  • 효율성
    • 탐색해야 하는 경우가 너무 많다. => DP
    • 추가로, 점화식에 필요한 값을 빠르게 계산해 내는 방법으로 Two Pointer 기법

생각의 흐름 - 음식을 먹게 되는 구간들

  • 음식을 먹기로 결정하면 그 순간부터 어디까지 먹게 되는 지를 무조건 정해진다는 것을 알게된다.
  • L 번째 음식에서 먹기 시작했을 때, 어디까지 먹으면 만족도는 얼마를 얻을 수 있을까?
  • L을 1부터 N까지 이동시키면서 매번 R을 계산한다고 하자.
    • L이 1만큼 증가하면 R은 어떻게 될까?
      • 모든 음식이 양수의 만조도를 가지기 때문에, L이 커지면 R은 절대로 작아질 수 없다.
      • 즉, 이전의 L에 대한 R값을 이용해서 다음 L에 대한 R을 계산할 수 있다.

생각의 흐름 - DP라면?

  • 정직한 순서로 문제를 풀어나가면 된다.
  • 먼저, DP Table을 정의하자
    • Dy[i] = i번 위치까지 도착했을 때, 가능한 최대 만족도
    • 정답 : Dy[N]
    • 초기값 : Dy[0] = 0
    • 점화식: Dy[i] = max(i번 음식 안먹기, i번 음식까지 먹어서 만족도 얻기)

생각의 흐름 - 전체 흐름

  1. 음식을 먹게 되는 구간 [L, R] 을 찾고, 같은 R에 대해서는 뭉쳐서 저장해 놓는다.
  2. R을 1씩 증가시키면서, Dy[R]의 값을 계산해 나아간다.
  3. 이때 점화식은, max(Dy[R-1], max[L-1] + Eat(L,R)) 이다.
  4. 구간을 Two Pointer로 찾고 저장해 놓는다면 시간 복잡도는 O(N)이 된다.

구현


public class baekjoon20181 {

  static int N, K;
  static long[] arr;
  static long[] dy;

  static ArrayList<long[]>[] list;

  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());
    K = Integer.parseInt(st.nextToken());

    arr = new long[N + 1];
    dy = new long[N + 1];

    st = new StringTokenizer(br.readLine(), " ");
    for (int i = 1; i <= N; i++) {
      arr[i] = Long.parseLong(st.nextToken());
    }


    list = new ArrayList[N + 1];

    for (int i = 1; i <= N; i++) {
      list[i] = new ArrayList<>();
    }


    long sum = 0;
    for (int L = 1, R = 0; L <= N; L++) {

      while (R + 1 <= N && sum < K) sum += arr[++R];

      if (K <= sum) {
        list[R].add(new long[]{L, sum - K});
      }

      sum -= arr[L];

    }


    for (int R = 1; R <= N; R++) {

      dy[R] = dy[R - 1];

      for (long[] x : list[R]) {

        dy[R] = Math.max(dy[R], dy[(int) (x[0]) - 1] + x[1]);
      }
    }


    System.out.println(dy[N]);

  }


}



public class baekjoon20181_2 {

    static int N, K;
    static long[] arr;
    static long[] dy;

    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());
        K = Integer.parseInt(st.nextToken());

        arr = new long[N + 1];
        dy = new long[N + 1];

        st = new StringTokenizer(br.readLine(), " ");
        for (int i = 1; i <= N; i++) {
            arr[i] = Long.parseLong(st.nextToken());
        }

        int right = 1;
        long sum = 0, dyLeftMax = 0;

        for (int left = 1; left <= N; left++) {
            dyLeftMax = Math.max(dyLeftMax, dy[left - 1]);

            while (right <= N && sum < K) {
                sum += arr[right++];
            }

            if (sum >= K) {
                dy[right - 1] = Math.max(dy[right - 1], dyLeftMax + (sum - K));
            } else {
                break;
            }

            sum -= arr[left];

        }

        long result = 0;

        for (int i = 1; i <= N; i++) {
            result = Math.max(result, dy[i]);
        }

        System.out.println(result);


    }
    
}


백준 20182번 - 골목대장 호석


  • 수가 크다, 꼭 정답의 최대치를 확인하자, Long이 필요함을 미리 깨달아야 한다.

출제 의도

  • 기능성
    • 완전 탐색
  • 효율성
    • Dijkstra 알고르즘 활용
    • Dijkstra 알고르즘 활용 + 이분 탐색

생각의 흐름 - 완전 탐색 먼저

  • 항상, 완전 탐색 방법 부터 먼저 생각해보자.
  • DFS를 통해 가진 돈으로 시작점에서 도착점까지 갈 수 있는 모든 경로를 시도해보자.
  • 가능한 경로는 최대 N! 까지 가능하기 때문에 완전 탬색이 가능하다.

생각의 흐름 - 정답을 변수로 만들어보자.

  • 최단경로 -> Dijkstra 알고리즘
  • But, 문제에서 요구하는 “최대 골목 비용”이란 것을 고려할 수가 없다.
  • 따라서, Dijkstra 알고르즘을 쓰기 위해서 최대 골목 비용 X를 결정해 보자

생각의 흐름 - 정답을 변수로 만들어보자

  • 골목 최대치 X가 늘어나면 사용 가능한 간선도 늘어나기 때문에, 최단거리는 점점 줄어들게 된다.
  • 그렇다면, 모든 X에 대해 확인하지 말고 이분탐색을 통해 X를 찾을 수 있지 않을까? 즉, Parametric Search이다

구현


public class baekjoon20168 {

  static int N, M, A, B, C, result;

  static boolean[] visited;

  static ArrayList<int[]>[] list;


  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());
    M = Integer.parseInt(st.nextToken());
    A = Integer.parseInt(st.nextToken());
    B = Integer.parseInt(st.nextToken());
    C = Integer.parseInt(st.nextToken());

    list = new ArrayList[N + 1];
    visited = new boolean[N + 1];

    result = Integer.MAX_VALUE;

    for (int i = 1; i <= N; i++) {
      list[i] = new ArrayList<>();
    }

    for (int i = 1; i <= M; i++) {
      st = new StringTokenizer(br.readLine(), " ");
      int a = Integer.parseInt(st.nextToken());
      int b = Integer.parseInt(st.nextToken());
      int c = Integer.parseInt(st.nextToken());

      list[a].add(new int[]{b, c});
      list[b].add(new int[]{a, c});
    }

    visited[A] = true;
    dfs20168(A, C, -1);

    if (result == Integer.MAX_VALUE) result = -1;

    System.out.println(result);

  }

  private static void dfs20168(int node, int money, int cost) {
    if (node == B) {
      result = Math.min(result, cost);
      return;
    }

    if (money <= 0) return;

    for (int[] next : list[node]) {

      if (visited[next[0]] || next[1] > money) continue;

      visited[next[0]] = true;

      dfs20168(next[0], money - next[1], Math.max(cost, next[1]));

      visited[next[0]] = false;

    }


  }


}


public class baekjoon20182 {

  static int N, M, A, B;
  static long C;

  static ArrayList<Info>[] list;

  static long max = Long.MAX_VALUE;

  static long[] d;


  static class Info implements Comparable<Info> {

    int idx;
    long cost;

    public Info(int idx, long cost) {
      this.idx = idx;
      this.cost = cost;
    }

    @Override
    public int compareTo(Info o) {

      if (cost > o.cost) return 1;
      if (cost == o.cost) return 0;
      return -1;
    }
  }

  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());
    M = Integer.parseInt(st.nextToken());
    A = Integer.parseInt(st.nextToken());
    B = Integer.parseInt(st.nextToken());
    C = Long.parseLong(st.nextToken());

    list = new ArrayList[N + 1];
    d = new long[N + 1];

    for (int i = 1; i <= N; i++) {
      list[i] = new ArrayList<>();
    }

    for (int i = 1; i <= M; i++) {
      st = new StringTokenizer(br.readLine(), " ");
      int a = Integer.parseInt(st.nextToken());
      int b = Integer.parseInt(st.nextToken());
      int c = Integer.parseInt(st.nextToken());

      list[a].add(new Info(b, c));
      list[b].add(new Info(a, c));
    }


    long left = 1;
    long right = 1000000001;
    long result = right;

    while (left <= right) {
      long mid = (left + right) / 2;

      if (dijkstra(mid)) {
        result = mid;
        right = mid - 1;
      } else {
        left = mid + 1;
      }

    }

    if (result == 1000000001) System.out.println(-1);
    else System.out.println(result);


  }

  private static boolean dijkstra(long x) {

    PriorityQueue<Info> queue = new PriorityQueue<>();

    Arrays.fill(d, max);
    d[A] = 0;
    queue.add(new Info(A, 0));
    while (!queue.isEmpty()) {

      Info cur = queue.poll();

      if (cur.cost != d[cur.idx]) continue;

      for (Info next : list[cur.idx]) {
        if (next.cost > x) continue;

        if (d[next.idx] > d[cur.idx] + next.cost) {
          d[next.idx] = d[cur.idx] + next.cost;
          queue.add(new Info(next.idx, d[next.idx]));
        }

      }

    }

    return d[B] <= C;
  }


}



© 2020. All rights reserved.

SIKSIK