알고리즘 유형별 문제풀이
류호석배 3회 모의 코테
[Part3 Ch03 류호석배 코딩테스트 모의고사] 류호석배 3회 모의 코테
백준 22251번 - 빌런 호석
출제 의도
- 올바른 접근 방법을 떠올리는가?
- 시간 & 공간 복잡도는 제대로 계산하였는가?
- 문제를 편하게 구현하였는가?
접근 방법
- X층을 Y층으로 바꿀 수 있는가?
총 정리
- diff_one 함수 구현하기
- 이른 이용한 diff 함수 구현
- Y를 1부터 N까지 바꿔보면서 변환 횟수가 P 이하인지 확인하기
- 총 시간 복잡도는 O(N * K) 이다.
구현
public class baekjoon22251 {
static int N, K, P, X;
static int[][] num_flag = {
{1, 1, 1, 0, 1, 1, 1},
{0, 0, 1, 0, 0, 1, 0},
{1, 0, 1, 1, 1, 0, 1},
{1, 0, 1, 1, 0, 1, 1},
{0, 1, 1, 1, 0, 1, 0},
{1, 1, 0, 1, 0, 1, 1},
{1, 1, 0, 1, 1, 1, 1},
{1, 0, 1, 0, 0, 1, 0},
{1, 1, 1, 1, 1, 1, 1},
{1, 1, 1, 1, 0, 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()); // 층수
K = Integer.parseInt(st.nextToken()); // 자리수
P = Integer.parseInt(st.nextToken()); // LED 변경
X = Integer.parseInt(st.nextToken()); // 현재 층수
int result = 0;
for (int i = 1; i <= N; i++) {
if (i == X) continue;
if (numericConversion(i, X) <= P) result++;
}
System.out.println(result);
}
private static int numericConversion(int x, int y) {
int n = 0;
for (int i = 1; i <= K; i++) {
n += numberOfConversions(x % 10, y % 10);
x /= 10;
y /= 10;
}
return n;
}
private static int numberOfConversions(int x, int y) {
int n = 0;
for (int i = 0; i < 7; i++) n += num_flag[x][i] != num_flag[y][i] ? 1 : 0;
return n;
}
}
백준 22252번 - 정보 상인 호석
출제 의도
- 문제가 요구하는 상황을 이해 했는가?
- 문자열을 잘 다루는가?
- 필요한 연산을 정의할 줄 아는가?
- 자료구조를 나열하고 시간 복잡도를 따져서 최선의 자료구조를 선택할 수 있는가?
접근 방법
- 먼저, 고릴라들의 이름을 어떻게 다룰 것인가?
- 문자열 그 자체를 이용하는 경우
- 숫자로 변경해서 이용하는 경우
- 사람마다 익숙한 것으로
총 정리
- 고릴라 마다 가지고 있는 정보의 가치들을 자료 구조(Max Heap)에 담아두고 있다.
- 총 시간은 정보를 삽입하고 삭제하는 과정에서 소모되는 시간이다. 정보가 많아야 100만개 삽입 & 삭제 되기 때문에 O(100만 log 100만)에 비례하는 시간이 걸리고, 시간 안에 충분히 나온다
구현
public class baekjoon22252 {
static HashMap<String, Integer> map = new HashMap<>();
static PriorityQueue<Integer>[] queues = new PriorityQueue[100005];
static int keyNum = 0;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int Q = Integer.parseInt(br.readLine());
long result = 0;
while (Q-- > 0) {
StringTokenizer st = new StringTokenizer(br.readLine(), " ");
int kind = Integer.parseInt(st.nextToken());
String name = st.nextToken();
if (!map.containsKey(name)) {
map.put(name, ++keyNum);
queues[keyNum] = new PriorityQueue<>(Comparator.reverseOrder());
}
int key = map.get(name);
if (kind == 1) {
int k = Integer.parseInt(st.nextToken());
while (k-- > 0) {
queues[key].add(Integer.parseInt(st.nextToken()));
}
} else {
int b = Integer.parseInt(st.nextToken());
while (b-- > 0 && !queues[key].isEmpty()) {
result += queues[key].poll();
}
}
}
System.out.println(result);
}
}
백준 22253번 - 트리 디자이너 호석
출제 의도
- Rooted Tree 구조에 익숙한가?
- 완전 탐색에서 동적 프로그래밍으로 연결을 지을수 있는가?
- 동적 프로그래밍 풀이를 스스로 수행할 수 있는가?
생각의 흐름 - 1. 정답의 최대치
- 가능한 경우가 가장 많은 입력은?
- 정점 10만개가 일렬로 존재하고, 모두 0인 경우
- 정점을 어떻게 선택해도 조건을 만족하므로 2^100000 가지 가능하다.
생각의 흐름 - 2. 동적 프로그래밍
- 모든 경우 하나씩 찾으면 당연히 “시간 초과”를 받을 것을 예상하기 쉽다.
- 이런 경우 동적 프로그래밍 접근을 시도해 볼 가치가 있다.
생각의 흐름 - 3. 문제 정의
- Dy[i][k] = i번 정점을 root로 하는 subtree에서, 조건을 만족하게 선택하면 마지막 숫자가 k인 경우의 수
- 정답 = Dy[1][0] + … + Dy[1][9]
- 초기화 = Dy[1][a[i]] = 1 (i번 정범만 선택하는 경우)
총 정리
- Rooted Tree 에서의 Dynamic Programming
- 시간 복잡도는 모든 정점을 한 번씩 보기 때문에 O(N)이다
구현
public class baekjoon22253 {
static int N;
static int[] a;
static int[][] dy;
static ArrayList<Integer>[] tree;
static final int MOD = 1000000007;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
N = Integer.parseInt(br.readLine());
a = new int[N + 1];
dy = new int[N + 1][10];
tree = new ArrayList[N + 1];
for (int i = 1; i <= N; i++) tree[i] = new ArrayList<>();
StringTokenizer st = new StringTokenizer(br.readLine(), " ");
for (int i = 1; i <= N; i++) {
a[i] = Integer.parseInt(st.nextToken());
}
for (int i = 1; i < N; i++) {
st = new StringTokenizer(br.readLine(), " ");
int x = Integer.parseInt(st.nextToken());
int y = Integer.parseInt(st.nextToken());
tree[x].add(y);
tree[y].add(x);
}
dfs22253(1, -1);
int result = 0;
for (int i = 0; i <= 9; i++) {
result += dy[1][i];
result %= MOD;
}
System.out.println(result);
}
private static void dfs22253(int x, int par) {
dy[x][a[x]] = 1;
for (int y : tree[x]) {
if (par == y) continue;
dfs22253(y, x);
for (int i = 0; i <= 9; i++) {
dy[x][i] += dy[y][i];
dy[x][i] %= MOD;
}
for (int i = a[x]; i <= 9; i++) {
dy[x][a[x]] += dy[y][i];
dy[x][a[x]] %= MOD;
}
}
}
}
백준 22254번 - 공정 컨설턴트 호석
출제 의도
- 공정 라인의 개수와 마감 시간의 관계를 관찰했는가?
- Parametric Search 기법을 떠올리고 올바르게 적용시켰는가?
생각의 흐름 - 1. 정답의 최대치
- 어떤 입력이 주어져도 공정 라인이 선뭉릐 개수만큼 존재하면 성공적으로 제작할 수 있다.
- 따라서 정답의 최대치는 N의 최대치인 10만이다.
생각의 흐름 - 2. 관찰
- 공정 라인의 개수를 최소화 시켜야 한다.
- 단, 총 제작 시간에 제한이 걸려 있다.
- 그렇다면 공정 라인의 개수와 총 제작 시간은 무슨 관계인가?
생각의 흐름 - 3. 문제 정의
- 공정 라인을 K개 사용하면 시간을 맞출 수 잇는가?
생각의 흐름 - 4. Parametric Search
- 공정 라인 개수가 정해져 있는 경우에는 모든 것이 결정적으로 (deterministic 하게) 진행되기 때문에 “시뮬레이션” 문제로 변경된다.
- 만약 한 번의 K에 대한 대답을 O(T)에 할 수 있다면?
- 이분 탐색을 통해 O(log N)번의 수행이 이뤄지므로 O(T log N) 이면 K^* 를 계산할 수 있다.
- K가 정해졌기 때문에, 1번부터 N번 선물을 차례대로 제작하면 된다.
- 필요한 연산
- 공정 라인 중에 사용 시간이 가장 적은 것을 찾는다.
- 특정 공정 라인의 사용 시간을 X 만큼 증가 시킨다.
- 두 연산을 모두 빠르게 (O(log K)) 해주는 Min Heap를 사용하자
총 정리
- K^*를 Parametric Search를 통해 찾는다.
- K에 대해서
- 공정 라인들의 사용 시간을 저장할 Min Heap 생성
- 0을 K번 삽입
- N개의 선물을 제작해보고 목표 시간 X와 비교
- O(N log N)
- 총 시간은 O(N log k log N) 이다
구현
public class baekjoon22254 {
static int N, X;
static int[] a;
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());
X = Integer.parseInt(st.nextToken());
a = new int[N + 1];
st = new StringTokenizer(br.readLine(), " ");
for (int i = 1; i <= N; i++) {
a[i] = Integer.parseInt(st.nextToken());
}
int L = 1;
int R = N;
int result = N;
while (L <= R) {
int mid = (L + R) / 2;
if (check(mid)) {
R = mid - 1;
result = mid;
} else {
L = mid + 1;
}
}
System.out.println(result);
}
private static boolean check(int num) {
PriorityQueue<Integer> queue = new PriorityQueue<>();
for (int i = 1; i <= num; i++) {
queue.add(0);
}
for (int i = 1; i <= N; i++) {
int poll = queue.poll();
if (poll + a[i] > X) return false;
queue.add(poll + a[i]);
}
return true;
}
}
백준 22255번 - 호석 사우로스
출제 의도
- 최단 시간 키워드를 통해 접근 방향을 잡았는가?
- 문제의 요구 조건에 맞게 거리 배열 정의를 했는가?
- 구현을 편하게 했는가?
생각의 흐름 - 키워드 발견
- 자신의 철칙을 지키되, 아픈 건 싫어하는 호석사우루스를 도와서 탈출구까지의 최소 충격량을 구해주자
- 주어진 문제를 “그래프 문제”로 변환시켜서 “최단 거리” 계산으로 방향을 잡아보자
생각의 흐름 - 최단 거리 알고리즘
- 최단 거리 알고리즘 복기
- 입력
- 그래프 & 시작점 & 도착점
- 출력
- 최단 거리
- 배운 것 중에는 BFS & Dijkstra 존재
- 그중 간선의 가중치가 다를 수 있다는 점을 통해 Dijkstra 선택
__생각의 흐름 - Dijkstra 알고리즘 거리 정의 __
- Dijkstra 알고리즘은 distance 배열이 필요하다.
- dist[i][j] = i 행 j 열까지의 최소 충격량(최단 거리) 으로 정의하면 되는가?
- 같은 위치에 대해서도 몇 번째 도착지인지가 중요하기 때문에 정보에 손실이 생긴다
- dist[i][j][move] = i 행 j 열에 (3K+move) 번의 이동으로 도착하는 방법 중에서 최소 충격량(최단 거리)
시간, 공간 복잡도 계산하기
- 정점의 개수 N * M 3 개라고 생각하면 된다.
- 즉 총 시간 복잡도는 O(NMlogNM)이다.
구현
public class baekjoon22255 {
static int N, M, SX, SY, EX, EY;
static int[][] a;
static int[][][] dy;
static int dir[][][] = {
{ { 1, 0 }, { -1, 0 } },
{ { 0, 1 }, { 0, -1 } },
{ { 1, 0 }, { -1, 0 }, { 0, -1 }, { 0, 1 } }
};
static class Info {
int x, y, move, dist;
public Info(int x, int y, int move, int dist) {
this.x = x;
this.y = y;
this.move = move;
this.dist = dist;
}
}
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());
st = new StringTokenizer(br.readLine(), " ");
SX = Integer.parseInt(st.nextToken());
SY = Integer.parseInt(st.nextToken());
EX = Integer.parseInt(st.nextToken());
EY = Integer.parseInt(st.nextToken());
a = new int[N + 2][M + 2];
dy = new int[N + 2][M + 2][3];
for (int i = 1; i <= N; i++) {
st = new StringTokenizer(br.readLine(), " ");
for (int j = 1; j <= M; j++) {
a[i][j] = Integer.parseInt(st.nextToken());
}
}
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= M; j++) {
for (int k = 0; k < 3; k++) {
dy[i][j][k] = Integer.MAX_VALUE;
}
}
}
dy[SX][SY][0] = 0;
PriorityQueue<Info> queue = new PriorityQueue<>(Comparator.comparing(info -> info.dist));
queue.add(new Info(SX, SY, 0, 0));
while (!queue.isEmpty()) {
Info poll = queue.poll();
int x = poll.x, y = poll.y, move = poll.move, dist = poll.dist;
if (dist != dy[x][y][move]) continue;
int choice = move == 2 ? 4 : 2;
for (int k = 0; k < choice; k++) {
int nx = x + dir[move][k][0];
int ny = y + dir[move][k][1];
if (nx < 0 || nx > N || ny < 0 || ny > M) continue;
if (a[nx][ny] == -1) continue;
int nmove = (move + 1) % 3;
int ndist = dist + a[nx][ny];
if (ndist >= dy[nx][ny][nmove]) continue;
dy[nx][ny][nmove] = ndist;
queue.add(new Info(nx, ny, nmove, ndist));
}
}
int result = Integer.MAX_VALUE;
for (int i = 0; i < 3; i++) {
result = Math.min(result, dy[EX][EY][i]);
}
if (result == Integer.MAX_VALUE) result = -1;
System.out.println(result);
}
}