문제 링크 : https://www.acmicpc.net/problem/11920


버블 정렬의 중간 과정을 구하는 문제입니다.


N이 최대 10,000 이기 때문에 일반적인 버블 정렬을 돌리면 당연히 터집니다.


이 문제는 버블 정렬의 특징을 이해해야 합니다.


버블 정렬 한번 도는 과정이 총 N번 진행된다고 할 때, 정렬을 한번 수행할 때마다 가장 큰값은 맨 뒤로, 더 작은 값들은 한번씩 앞으로 이동합니다.


만약 K 번 수행한다면 가장 큰 수부터 K 번째 수까지 전부 뒤로 정렬된 상태입니다.


그러므로 우선순위큐를 이용하여 사이즈가 K만큼 유지되도록 만드는게 핵심입니다.


만약 우선순위큐에 K개의 숫자가 들어있는데 새로운 값이 들어온다고 가정합니다.


1. 기존의 K개의 숫자들보다 작은 값이 들어온다.


 => pq.poll() 하기 때문에 바로 출력됨


2. 기존의 K개의 숫자들과 비슷한 값이 들어온다.


 => pq.poll() 할 때 기존에 있던 값 중 가장 작은 값이 출력됩니다.


가장 작은 값이 출력되는 것은 똑같습니다.


하지만 핵심은 pq에는 항상 가장 큰 K개의 숫자들만 유지된다는 사실입니다.


만약 pq에 [4 5 6 7] 이렇게 들어있고 새로운 값으로 1, 3, 2 순으로 들어온다면 [1, 3, 2, 4, 5, 6, 7] 순으로 출력될겁니다.


문제 링크 : https://www.acmicpc.net/problem/1377


백준 1838번 과 똑같은 유형의 문제입니다.


요점은 정렬이 완료되었을 때의 i를 구하는 것입니다.


하지만 1838번과 다른 점이 한가지 있습니다.


1838번은 문제에서 서로 다른 N 개의 숫자가 들어온다고 되어있지만 1377번은 그런 설명이 없습니다.


즉, 중복된 숫자가 들어올 수 있다는 뜻입니다.


그러므로 중복된 숫자가 들어왔을 때 stable 정렬을 유지하기 위해 인덱스가 큰 건 뒤로 가도록 compareTo 조건을 살짝 수정해주면 됩니다.


문제 링크 : https://www.acmicpc.net/problem/1517


문제 이름은 버블소트지만 버블소트로 풀면 틀리는 문제입니다.


N이 최대 500,000 이죠 O(n^2)을 돌리면 당연하게도 시간초과가 납니다.


우리가 구해야 하는건 swap하는 갯수입니다.


버블소트에서 swap이 발생하는 조건은 앞의 숫자가 뒤에 숫자보다 클 때입니다.


즉 a[i] > a[i+1] 일 때 swap이 발생합니다.


그러므로 머지 소트를 이용하여 정렬을 하되, 앞의 숫자가 뒤의 숫자보가 큰 경우에 카운트를 해주면 동일하게 swap 횟수를 구할 수 있습니다.


swapCount에 (mid+1-i) 를 더하는 이유는 왼쪽 배열에 남아있는 숫자만큼 계속 swap이 발생하기 때문입니다.


문제 링크 : https://www.acmicpc.net/problem/1838


문제 이름은 버블 정렬이지만 버블 정렬로 풀면 틀리는 문제입니다.


버블 정렬의 시간복잡도는 O(N^2)이고 N은 최대 500,000 이기 때문에 살짝만 계산해봐도 시간초과가 나온다는 사실을 알 수 있습니다.


이 문제는 버블 정렬의 특징을 이용해서 풀어야 합니다.


버블정렬은 간단히 설명하자면 0번 인덱스부터 N-2번 인덱스까지 진행하면서 arr[i] 와 arr[i+1]을 비교하여 swap 하는 정렬입니다.


위와 같은 과정을 최대 N번 반복하면 정렬된 배열을 얻을 수 있습니다.


이때 문제에 나와있는 것처럼 flag를 써서 swap 여부를 체크하는 것처럼 약간의 최적화를 할 수 있습니다.


아무튼 다시 문제로 돌아오자면 핵심은 몇 번만에 정렬이 완료되었는가를 알아내는 것입니다.


그리고 정렬이 완료되었다는 것은 for문을 아무리 돌아도 각 변수의 이동이 없다는 것을 의미합니다.


이걸 또 다른말로 하면 변수의 이동이 존재한다면 정렬은 끝나지 않았다 가 됩니다.


서론이 길었는데 간단히 말하자면


정렬하기 전 인덱스와 정렬 후 인덱스를 비교해서 가장 많이 이동한 횟수를 출력한다


가 해법입니다.


그런데 무조건 많이 이동한 횟수는 아닙니다.


예를 들어 5 4 3 2 1 은 한바퀴 돌면 4 3 2 1 5가 되는데 가장 큰수는 마지막까지 이동하기 때문에 이동횟수가 가장 많습니다.


하지만 실제로는 한바퀴만 돌았죠


버블 정렬을 한 바퀴 돌때마다 큰 수들은 뒤로 끝까지 쭉쭉 밀립니다.


하지만 작은 수는 앞으로 한칸씩 땡겨집니다.


그렇기 때문에


정렬하기 전 인덱스 - 정렬 후 인덱스 > 0 인 후보들 중에서 고릅니다.


그렇다면 가장 작은 수를 기준으로 하면 되지 않느냐 할수도 있는데 그건 또 아닙니다.


예를 들어 3 1 4 2 5 를 정렬하면


가장 작은 수인 1은 앞으로 한칸만 간 후에 더이상 이동하지 않습니다.


하지만 완전한 정렬이 되기 위해서 2는 두칸을 이동하죠


그렇기 때문에 max 값을 구하는 겁니다.


가장 많이 이동한 값이 곧 마지막 까지 이동한 값이고 이것이 정렬을 하기 위해 얼만큼 반복했는지를 나타냅니다.


저는 현재 index와 value를 갖고 Comparable 인터페이스를 상속받아 value 를 기준으로 정렬되는 클래스를 만들었습니다.


그리고 입력받은 값을 우선순위큐에 담으면 value를 기준으로 정렬된 자료구조를 구할 수 있습니다.


그리고 기존에 갖고 있던 index값과 현재 idx값을 비교하여 최대값을 찾아 출력하였습니다.


Problem


Z 모양으로 순회하는 순서를 알아내는 문제입니다.



Solution

처음 보는 순간 재귀가 떠오릅니다.

사각형을 4개의 사분면으로 나누어서 순서대로 들어가고 또 그 사각형 내에서 4등분 하여 접근합니다.

그렇게 사각형을 더이상 나누지 못하는 1이 될 때까지 재귀를 들어간 후에 카운트 해주면 됩니다.

(x, y, size);

(0, 0, 8);
      (0, 0, 4);
          (0, 0, 2);
              (0, 0, 1);
              (1, 0, 1);
              (0, 1, 1);
              (1, 1, 1);
          (2, 0, 2);
          (0, 2, 2);
          (2, 2, 2);
      (4, 0, 4);
          (4, 0, 2);
          (6, 0, 2);
          (4, 2, 2);
          (6, 2, 2);
      (0, 4, 4);
      (4, 4, 4);

간단하게 표현한건데 재귀에 들어가는 모습을 시각적으로 표현한 겁니다.

전부 다 나타낸 것은 아니고 일부만 나타낸 거지만 size가 1이 될 때까지 계속해서 사각형을 나누어 접근합니다.

규칙이 머리속에 바로 떠오르지 않는다면 이렇게 간단하게 적어보시면 금방 찾을 수 있습니다.


하지만 N 이 최대 15라서 재귀를 사용하며 시간 초과가 발생합니다.


시간을 줄이기 위해서 일일히 좌표에 접근하지 않고 한번에 가는 방법을 생각해보았습니다.


규칙이 보이시나요?

8 * 8 배열 일 때 각 사분면의 첫번째 값은 0, 16, 32, 48 입니다.

4 * 4 배열 일 때 각 사분면의 첫번째 값은 0, 4, 8, 12 입니다.

2 * 2 배열 일 때 각 사분면의 첫번째 값은 0, 1, 2, 3 입니다.

n * n 배열 일 때 각 사분면의 첫번째 값은

(n/2) * (n/2) * 0
(n/2) * (n/2) * 1
(n/2) * (n/2) * 2
(n/2) * (n/2) * 3

이렇게 됩니다.

count 에 대한 식을 이렇게 얻었습니다.


Exmaple

n 이 8 인 경우 (2, 3) 좌표를 찾아가보겠습니다.


Step 1. n = 8

먼저 (2, 3) 좌표는 (n / 2, n / 2) 인 (4, 4) 좌표보다 x, y 좌표가 둘다 작습니다.

그래서 왼쪽 위 사각형으로 이동하고 nn /= 2 로 절반으로 줄어듭니다.


Step 2. n =4

그리고 다시 (2, 3) 좌표는 (n / 2, n / 2) 인 (2, 2) 좌표보다 x, y 좌표가 둘다 크거나 같습니다.

그래서 오른쪽 아래 사각형으로 이동하고 nn /= 2 로 절반으로 줄어듭니다.


Step 3. n = 2

오른쪽 아래 사각형은 (2, 2) 좌표가 첫번째 값입니다.

그러므로 (2 + n / 2, 2 + n / 2) 인 (3, 3) 좌표와 값을 비교합니다.

(2, 3) 좌표는 (3, 3) 좌표보다 x 좌표는 작지만 y 좌표는 크거나 같기 때문에 오른쪽 위 사각형으로 이동합니다.

그리고 n 을 절반으로 하면 1 이 되기 때문에 더 이상 나눌 수 없고 해당 좌표의 카운트를 출력합니다.

말로 설명하기 어려운 부분이 있는데 그림을 보며 코드를 따라가시면 금방 이해할 수 있습니다.



Java Code

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

class Main {
    static int stoi(String s) { return Integer.parseInt(s);}

    static int N;
    static int r;
    static int c;
    static int count = 0;

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

        int N = stoi(st.nextToken());
        int r = stoi(st.nextToken());
        int c = stoi(st.nextToken());

        int n = getSize(N);
        int count = 0;
        int x = 0;
        int y = 0;

        /**
         * 사각형 절반으로 나눠서 각 사분면으로 계산
         * n 이 1 이 된다는 것은 x, y 좌표가 r, c랑 같아진다는 것
         */
        while (n > 0) {
            n /= 2;

            // 2사분면 (왼 위)
            if (r < x+n && c < y+n) {
                count += n * n * 0;
            }
            // 1사분면 (오른 위)
            else if (r < x+n) {
                count += n * n * 1;
                y += n;
            }
            // 3사분면 (왼 아래)
            else if (c < y+n) {
                count += n * n * 2;
                x += n;
            }
            // 4사분면 (오른 아래)
            else {
                count += n * n * 3;
                x += n;
                y += n;
            }

            if(n == 1) {
                System.out.println(count);
                break;
            }
        }
    }

    static int getSize(int n) {
        int result = 1;
        for(int i=0; i<n; i++) {
            result *= 2;
        }
        return result;
    }
}

문제 링크 : https://www.acmicpc.net/problem/2667


단지의 개수와 각 단지별 집의 개수를 출력하는 문제입니다.


DFS로 각 단지의 개수를 세주면 됩니다.


한번 방문한 집은 또 방문할 일이 없기 때문에 일반적으로 사용하는 visited 배열을 쓰지 않고


대신 arr 값을 1에서 0으로 바꿔주면서 DFS를 반복했습니다.


DFS를 한번 돌 때마다 단지 하나가 전부 0 이 됩니다.


그러므로 main에서 DFS를 한번 접근 할 때마다 카운트하여 단지 수를 저장하면 됩니다.


단지별 집의 개수는 오름차순으로 출력하기 때문에 바로바로 출력하지 말고 따로 저장해야합니다.


ArrayList에 담은 후에 Collections.sort 를 사용하여 정렬을 해도 되지만


저는 우선순위큐를 사용하여 오름차순으로 출력하였습니다.


'알고리즘 문제 > 백준' 카테고리의 다른 글

백준 1838번. 버블 정렬 (Java)  (0) 2019.01.16
백준 1074번. Z (Java)  (4) 2019.01.13
백준 1302번. 베스트셀러 (Java)  (0) 2019.01.10
백준 1057번. 토너먼트 (Java)  (0) 2019.01.09
백준 2026번. 소풍 (Java)  (0) 2019.01.09

문제 링크 : https://www.acmicpc.net/problem/1302


가장 많이 나온 책을 출력하는 문제입니다.


책을 Key로, 횟수를 Value로 저장하는 HashMap<String, Integer> 를 사용하여 해결할 수 있습니다.


한가지 주의사항은 value가 같을 때는 패스하는게 아니라 책을 비교하여 사전순으로 앞서는 것으로 갱신해야 한다는 점입니다.


HashMap 반복문은 개인적으로 제가 가장 편하게 생각하는 것으로 구현하였습니다.


Iterator나 Element를 사용해도 상관 없습니다.


문제 링크 : https://www.acmicpc.net/problem/1057


김지민, 임한수가 라운드 몇에서 대결하게 될 지 맞추는 문제입니다.


처음에 단순하게 Queue로 구현을 하였습니다.


1. 모든 토너먼트 멤버를 Queue에 넣은 뒤 두 개씩 poll 한다.

2. 두 값을 비교하여 김지민, 임한수라면 라운드를 출력하고 종료

2-1. 두 값 중 하나만 김지민 혹은 임한수라면 무조건 승리자

2-2. 둘 다 해당되지 않는다면 무조건 앞 번호가 승리자


이렇게 단순하게 구현하여 Queue에 계속 넣었다 빼며 구현하였습니다.


하지만 정답을 맞춘 후에 다른 정답자들 코드를 보니 굉장히 간단하게 구현하였습니다.


각 멤버가 같은 라운드에서 만난다는 것은 부모 노드가 같다고 볼 수 있습니다.


그래서 두 멤버의 부모 노드만을 계속 따라가며 같은 값이 될 때의 라운드를 출력하면 됩니다.


자식 노드가 최대 두 개로 고정되어 있기 때문에 부모 노드의 인덱스는


(now + 1) / 2

now/2 + now%2


이렇게 두 가지 방법으로 구할 수 있습니다.


코드는 처음에 구현했던 Queue를 이용한 방법과 두번째로 구현한 방법 모두 올리겠습니다.


+ Recent posts