𝝅번째 알파카의 개발 낙서장

screen

[백준 / JAVA] 백준 알고리즘 1021번 회전하는 큐

posts

알고리즘

시리즈 톺아보기

백준 알고리즘

백준 알고리즘
count

회전하는 큐 🔗

랭크 사용 언어
image JAVA

🔗 전체 1021번 문제

조건 🔗

시간제한 메모리 제한
2초 128MB

문제 🔗

지민이는 개의 원소를 포함하고 있는 양방향 순환 큐를 가지고 있다. 지민이는 이 큐에서 몇 개의 원소를 뽑아내려고 한다.

지민이는 이 큐에서 다음과 같은 3가지 연산을 수행할 수 있다.

  1. 첫 번째 원소를 뽑아낸다. 이 연산을 수행하면, 원래 큐의 원소가 였던 것이 가 된다.
  2. 왼쪽으로 한 칸 이동시킨다. 이 연산을 수행하면 이 된다.
  3. 오른쪽으로 한 칸 이동시킨다. 이 연산을 수행하면 이 된다.

큐에 처음에 포함되어 있던 수 이 주어진다. 그리고 지민이가 뽑아내려고 하는 원소의 위치가 주어진다. (이 위치는 가장 처음 큐에서의 위치이다.) 이때, 그 원소를 주어진 순서대로 뽑아내는데 드는 2번, 3번 연산의 최솟값을 출력하는 프로그램을 작성하시오.

입력 🔗

첫째 줄에 큐의 크기 과 뽑아내려고 하는 수의 개수 이 주어진다. 은 50보다 작거나 같은 자연수이고, 보다 작거나 같은 자연수이다. 둘째 줄에는 지민이가 뽑아내려고 하는 수의 위치가 순서대로 주어진다. 위치는 1보다 크거나 같고, N보다 작거나 같은 자연수이다.

출력 🔗

첫째 줄에 문제의 정답을 출력한다.

케이스 🔗

예제 1 🔗

INPUT

010 3
11 2 3

OUTPUT

00

예제 2 🔗

INPUT

010 3
12 9 5

OUTPUT

08

예제 3 🔗

INPUT

032 6
127 16 30 11 6 23

OUTPUT

059

예제 4 🔗

INPUT

010 10
11 6 3 2 7 9 8 4 10 5

OUTPUT

014

풀이 🔗

의 특성을 알고 있다면 이해하기 쉬운 문제다.

큐는 배열의 형태로, 한쪽 입구에서 요소의 삽입이 일어나며, 다른 한쪽에서 요소의 삭제가 일어나는 자료구조다. 컨테이너 벨트처럼 순차적으로 데이터를 처리하는 선입선출(FIFO) 방식을 차용하고 있어 순차적인 데이터를 처리하는데 유용하다.

큐의 특성은 해당 블로그에 작성된 게시글에서 확인할 수 있다.

image

하지만 단순한 큐가 아니라, 아래의 특징을 갖는 자료구조를 설계해야한다.

  1. 첫 번째 원소를 뽑아내는 연산
  2. 데이터의 좌우 이동 연산 (각 끝의 요소는 반대편으로 이동하는 방식으로 순환)

위 자료구조를 토대로 알고리즘이 원하는 동작을 구현하면 된다.

문제에서 제시하는 데이터를 순서대로 뽑아내되, 최소한의 데이터 이동이 이루어져야한다.

1번 연산을 수행하면, 단순히 첫 번째 칸의 원소만 지우는 게 아니라, 칸 자체를 제거한다.

즉, 10개의 칸을 가진 큐에서 1번 연산을 수행하면 칸이 9개로 감소한다.

예제 풀이 🔗

image

예제 2를 기준으로 풀이를 진행한다. 큐의 길이는 총 10으로, 위와 같을 것이다.

image

뽑을 원소의 순서는 위와 같다.



1. 2의 위치 계산

첫 번째로 뽑을 원소인 2와 데이터의 삭제 연산이 일어나는 첫 번째 칸까지의 위치를 계산한다.

image

  • 오른쪽: 9칸
  • 왼쪽: 1칸

왼쪽이 더 빠르므로, 방향을 왼쪽으로 정한다.



2. 요소 왼쪽으로 이동

image

  • 누적 이동횟수: 1

요소 2를 첫 번째 칸까지 왼쪽으로 1칸 이동한다.



3. 요소 삭제

image

요소 2를 삭제한다. 아예 칸 자체가 사라짐에 유의하자.

삭제는 이동횟수에 포함되지 않는다.



4. 9의 위치 계산

두 번째로 뽑을 원소인 9와 데이터의 삭제 연산이 일어나는 첫 번째 칸까지의 위치를 계산한다.

image

  • 오른쪽: 3칸
  • 왼쪽: 6칸

오른쪽이 더 빠르므로, 방향을 오른쪽으로 정한다.



5. 요소 오른쪽으로 이동

image

  • 누적 이동횟수: 4

요소 9를 첫 번째 칸까지 오른쪽으로 3칸 이동한다.



6. 요소 삭제

image

요소 9를 삭제한다.



7. 5의 위치 계산

마지막으로 뽑을 원소인 5와 데이터의 삭제 연산이 일어나는 첫 번째 칸까지의 위치를 계산한다.

image

  • 오른쪽: 4칸
  • 왼쪽: 4칸

거리가 서로 동등한 케이스다. 이 경우 알고리즘 역시 별도의 제약조건을 걸지 않았으므로, 아무 방향으로 이동해도 상관없다.

본 문서에선 왼쪽을 기준으로 이동한다.



8. 요소 왼쪽으로 이동

image

  • 누적 이동횟수: 8

요소 9를 첫 번째 칸까지 오른쪽으로 4칸 이동한다.



9. 요소 삭제

image

요소 9를 삭제한다.



10. 결과 도출

총 이동횟수는 8회이므로, 알고리즘의 결과는 8이 된다.

전체 소스 🔗

JAVA

0import java.io.BufferedReader;
1import java.io.BufferedWriter;
2import java.io.IOException;
3import java.io.InputStreamReader;
4import java.io.OutputStreamWriter;
5import java.util.Arrays;
6import java.util.LinkedList;
7
8/**
9 * 백준 전체 1021 문제 알고리즘 클래스
10 *
11 * @author RWB
12 * @see <a href="https://blog.itcode.dev/posts/2021/07/14/A1021/">1021 풀이</a>
13 * @since 2021.07.14 12:57:01
14 */
15public class Main
16{
17 // 뽑을 수의 갯수
18 private static int M;
19
20 // 큐
21 private static final LinkedList<Integer> QUEUE = new LinkedList<>();
22
23 /**
24 * 메인 함수
25 *
26 * @param args: [String[]] 매개변수
27 *
28 * @throws IOException 데이터 입출력 예외
29 */
30 public static void main(String[] args) throws IOException
31 {
32 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
33 BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
34
35 // N과 M
36 int[] meta = Arrays.stream(reader.readLine().split(" ")).mapToInt(Integer::parseInt).toArray();
37
38 // 수의 위치
39 int[] position = Arrays.stream(reader.readLine().split(" ")).mapToInt(Integer::parseInt).toArray();
40
41 // 큐의 크기
42 int N = meta[0];
43
44 M = meta[1];
45
46 // 큐의 크기만큼 큐 초기화
47 for (int i = 0; i < N; i++)
48 {
49 QUEUE.add(i + 1);
50 }
51
52 writer.write(String.valueOf(solve(position)));
53 writer.newLine();
54
55 writer.close();
56 reader.close();
57 }
58
59 /**
60 * 큐 연산 갯수 반환 함수
61 *
62 * @param position: [int[]] 수의 위치 배열
63 *
64 * @return [int] 연산 갯수
65 */
66 private static int solve(int[] position)
67 {
68 int count = 0;
69
70 for (int i = 0; i < M; i++)
71 {
72 // 뽑을 요소의 인덱스
73 int target = QUEUE.indexOf(position[i]);
74
75 // 구간 구분 기준
76 int ref = QUEUE.size() / 2;
77
78 // 오른쪽으로 이동하는 게 더 빠를 경우
79 if (target > ref)
80 {
81 while (position[i] != QUEUE.getFirst())
82 {
83 // 맨 끝 요소를 제거하고 맨 앞에 추가
84 QUEUE.addFirst(QUEUE.removeLast());
85 count++;
86 }
87 }
88
89 // 왼쪽으로 이동하는 게 더 빠를 경우
90 else
91 {
92 while (position[i] != QUEUE.getFirst())
93 {
94 // 맨 앞 요소를 제거하거 맨 끝에 추가
95 QUEUE.addLast(QUEUE.removeFirst());
96 count++;
97 }
98 }
99
100 QUEUE.removeFirst();
101 }
102
103 return count;
104 }
105}

회전하는 큐의 주요 동작은 양방향 이동, 삭제다. 해당 동작은 각각 move(), pop() 메소드로 구현된다.

JAVA

0/**
1 * 이동 함수
2 *
3 * @param direction: [DIRECTION] 방향 Enum
4 * @param distance: [int] 거리
5 */
6private static void move(DIRECTION direction, int distance)
7{
8 // 왼쪽으로 이동할 경우
9 if (DIRECTION.LEFT == direction)
10 {
11 for (int i = 0; i < distance; i++)
12 {
13 QUEUE.addLast(QUEUE.removeFirst());
14 }
15 }
16
17 // 오른쪽으로 이동할 경우
18 else
19 {
20 for (int i = 0; i < distance; i++)
21 {
22 QUEUE.addFirst(QUEUE.removeLast());
23 }
24 }
25}

move() 수행 시 방향은 enum 객체인 DIRECTION으로 구분하며, 입력한 횟수만큼 이동한다.

JAVA

0/**
1 * 삭제 함수
2 */
3private static void pop()
4{
5 QUEUE.removeFirst();
6}

pop() 수행 시 큐의 첫 번째 요소를 삭제한다.


핵심 알고리즘 함수는 아래와 같다.

JAVA

0/**
1 * 큐 연산 갯수 반환 함수
2 *
3 * @param position: [int[]] 수의 위치 배열
4 *
5 * @return [int] 연산 갯수
6 */
7private static int solve(int[] position)
8{
9 int count = 0;
10
11 for (int i = 0; i < M; i++)
12 {
13 // 뽑을 요소의 인덱스
14 int target = QUEUE.indexOf(position[i]);
15
16 // 요소의 중간
17 int mid = QUEUE.size() / 2;
18
19 // 인덱스가 요소의 중간값을 넘을 경우 오른쪽이 더 빠름
20 DIRECTION direction = target > mid ? DIRECTION.RIGHT : DIRECTION.LEFT;
21
22 // 오른쪽으로 갈 경우 큐의 길이에서 인덱스를 빼서 역계산
23 int distance = direction == DIRECTION.RIGHT ? QUEUE.size() - target : target;
24
25 move(direction, distance);
26 pop();
27
28 // 이동 길이 누적
29 count += distance;
30 }
31
32 return count;
33}

target으로 뽑을 요소의 인덱스를 구한다.

mid 큐의 중간값으로, target이 중간값보다 클 경우 오른쪽으로, 아닐 경우 왼쪽으로 이동하도록 direction을 지정한다.

distance는 거리로, 오른쪽으로 이동할 경우 큐의 사이즈에서 거리를 빼고, 왼쪽으로 이동할 경우 거리를 그대로 사용한다.

이후 계산한 방향, 거리만큼 이동하고 데이터를 삭제한다. 이동거리는 count에 누적된다.

분류 🔗

  • 자료구조