코딩테스트

24060번 알고리즘 수업 병합 정렬 -1 , 4779번 칸토어 집합

Cadi 2025. 3. 3. 21:35

코딩테스트 :  24060번  알고리즘 수업 병합 정렬 -1    

문제

오늘도 서준이는 병합 정렬 수업 조교를 하고 있다. 아빠가 수업한 내용을 학생들이 잘 이해했는지 문제를 통해서 확인해보자.

N개의 서로 다른 양의 정수가 저장된 배열 A가 있다. 병합 정렬로 배열 A를 오름차순 정렬할 경우 배열 A에 K 번째 저장되는 수를 구해서 우리 서준이를 도와주자.

크기가 N인 배열에 대한 병합 정렬 의사 코드는 다음과 같다.

merge_sort(A[p..r]) { # A[p..r]을 오름차순 정렬한다.
    if (p < r) then {
        q <- ⌊(p + r) / 2⌋;       # q는 p, r의 중간 지점
        merge_sort(A, p, q);      # 전반부 정렬
        merge_sort(A, q + 1, r);  # 후반부 정렬
        merge(A, p, q, r);        # 병합
    }
}

# A[p..q]와 A[q+1..r]을 병합하여 A[p..r]을 오름차순 정렬된 상태로 만든다.
# A[p..q]와 A[q+1..r]은 이미 오름차순으로 정렬되어 있다.
merge(A[], p, q, r) {
    i <- p; j <- q + 1; t <- 1;
    while (i ≤ q and j ≤ r) {
        if (A[i] ≤ A[j])
        then tmp[t++] <- A[i++]; # tmp[t] <- A[i]; t++; i++;
        else tmp[t++] <- A[j++]; # tmp[t] <- A[j]; t++; j++;
    }
    while (i ≤ q)  # 왼쪽 배열 부분이 남은 경우
        tmp[t++] <- A[i++];
    while (j ≤ r)  # 오른쪽 배열 부분이 남은 경우
        tmp[t++] <- A[j++];
    i <- p; t <- 1;
    while (i ≤ r)  # 결과를 A[p..r]에 저장
        A[i++] <- tmp[t++]; 
}

입력

첫째 줄에 배열 A의 크기 N(5 ≤ N ≤ 500,000), 저장 횟수 K(1 ≤ K ≤ 108)가 주어진다.

다음 줄에 서로 다른 배열 A의 원소 A1, A2, ..., AN이 주어진다. (1 ≤ Ai ≤ 109)

출력

배열 A에 K 번째 저장 되는 수를 출력한다. 저장 횟수가 K 보다 작으면 -1을 출력한다.

using System.Text;

public class Solution
{
    private static int saveIndex = 0;
    public static void Main()
    {
      int[] input = Console.ReadLine().Split(' ').Select(int.Parse).ToArray();
      int targetIndex = input[1];
      
      int[] arr = Console.ReadLine().Split(' ').Select(int.Parse).ToArray();
      int answer = MergeSort(arr, 0, arr.Length - 1, targetIndex);
      Console.WriteLine(answer);

    }

    public static int MergeSort(int[] arr, int left, int right, int targetIndex)
    {
        
        if (left < right)
        {
            int middle = (left + right) / 2;
            MergeSort(arr, left, middle, targetIndex);
            MergeSort(arr, middle + 1, right, targetIndex);
           var answer = Merge(arr, left, middle, right, targetIndex);
           if (answer != -1) return answer;
        }

        return -1;
    }


    public static int Merge(int[] arr, int left, int middle, int right, int targetIndex)
    {
        int[] temp = new int[right - left + 1];
        // 복사해서 새로운 배열을 만든다. 

        int s = left;
        int m = middle + 1;
        int index = 0;

        while (s <= middle && m <= right)
        {
            if (arr[s] < arr[m])
            {
                temp[index] = arr[s];
                s++;

            }
            else
            {
                temp[index] = arr[m];
                m++;
            }
            saveIndex++;
            if (saveIndex == targetIndex) return temp[index];
            index++;
        }

        for (int i = s; i <= middle; i++)
        {
            temp[index] = arr[i];
            saveIndex++;
            if (saveIndex == targetIndex) return temp[index];
            index++;

        }

        for (int i = m; i <= right; i++)
        {
            temp[index] = arr[i];
            saveIndex++;
            if (saveIndex == targetIndex) return temp[index];
            index++;
        }
        for (int i = left; i <= right; i++)
        {
            arr[i] = temp[i - left]; // 원래 배열에 다시 복사해야 함
        }
        return  -1; 

    }
}

 

 

왜 안되는지 한참 고민했다. 

내가 생각했던건 결국 '저장'은 병합 과정에서만 하니까 병합 과정에서만 값을 저장하고, 그 값을 비교하고 리턴하면 되지 않을까 ? 라고 생각했다. 

그런데, 병합 과정에서 올바른 값을 찾아서 리턴해도, 그 리턴값이 상위 호출로 이어지지 않았다. 

그래서 진짜 삼십분 고민하다가 새로운 답으로 찾았다. 

using System.Text;

public class Solution
{
    private static int saveIndex = 0;
    public static void Main()
    {
      int[] input = Console.ReadLine().Split(' ').Select(int.Parse).ToArray();
      int targetIndex = input[1];
      
      int[] arr = Console.ReadLine().Split(' ').Select(int.Parse).ToArray();
      int answer = MergeSort(arr, 0, arr.Length - 1, targetIndex);
      Console.WriteLine(answer);

    }

    public static int MergeSort(int[] arr, int left, int right, int targetIndex)
    {
        
        if (left < right)
        {
            int middle = (left + right) / 2;
           var answer1 = MergeSort(arr, left, middle, targetIndex);
           if (answer1 != -1) return answer1;
           var answer2=  MergeSort(arr, middle + 1, right, targetIndex);
           if (answer2 != -1) return answer2;
           var answer = Merge(arr, left, middle, right, targetIndex);
           if (answer != -1) return answer;
        }

        return -1;
    }


    public static int Merge(int[] arr, int left, int middle, int right, int targetIndex)
    {
        int[] temp = new int[right - left + 1];
        // 복사해서 새로운 배열을 만든다. 

        int s = left;
        int m = middle + 1;
        int index = 0;

        while (s <= middle && m <= right)
        {
            if (arr[s] < arr[m])
            {
                temp[index] = arr[s];
                s++;

            }
            else
            {
                temp[index] = arr[m];
                m++;
            }
            saveIndex++;
            if (saveIndex == targetIndex) return temp[index];
            index++;
        }

        for (int i = s; i <= middle; i++)
        {
            temp[index] = arr[i];
            saveIndex++;
            if (saveIndex == targetIndex) return temp[index];
            index++;

        }

        for (int i = m; i <= right; i++)
        {
            temp[index] = arr[i];
            saveIndex++;
            if (saveIndex == targetIndex) return temp[index];
            index++;
        }
        for (int i = left; i <= right; i++)
        {
            arr[i] = temp[i - left]; // 원래 배열에 다시 복사해야 함
        }
        return  -1; 

    }
}

 

이 문제는 예전에 봤던 병합 정렬을 다시 공부하게 해 준 고마운 문제다. 

곧 새롭게 포스팅해서 올릴지도.. . . ?

 

더해서, 재귀에 속성에 대해 다시 한 버 생각해보게 되었다. 하위 호출에서 상위 호출로 값을 전달할 때 어떻게 해야 하는지.. 

 

 

 

 

코딩테스트 : 4779번 칸토어 집합  

문제

칸토어 집합은 0과 1사이의 실수로 이루어진 집합으로, 구간 [0, 1]에서 시작해서 각 구간을 3등분하여 가운데 구간을 반복적으로 제외하는 방식으로 만든다.

전체 집합이 유한이라고 가정하고, 다음과 같은 과정을 통해서 칸토어 집합의 근사를 만들어보자.

1. -가 3N개 있는 문자열에서 시작한다.

2. 문자열을 3등분 한 뒤, 가운데 문자열을 공백으로 바꾼다. 이렇게 하면, 선(문자열) 2개가 남는다.

3. 이제 각 선(문자열)을 3등분 하고, 가운데 문자열을 공백으로 바꾼다. 이 과정은 모든 선의 길이가 1일때 까지 계속 한다.

예를 들어, N=3인 경우, 길이가 27인 문자열로 시작한다.

---------------------------

여기서 가운데 문자열을 공백으로 바꾼다.

---------         ---------

남은 두 선의 가운데 문자열을 공백으로 바꾼다.

---   ---         ---   ---

한번 더

- -   - -         - -   - -

모든 선의 길이가 1이면 멈춘다. N이 주어졌을 때, 마지막 과정이 끝난 후 결과를 출력하는 프로그램을 작성하시오.

입력

입력을 여러 줄로 이루어져 있다. 각 줄에 N이 주어진다. 파일의 끝에서 입력을 멈춘다. N은 0보다 크거나 같고, 12보다 작거나 같은 정수이다.

출력

입력으로 주어진 N에 대해서, 해당하는 칸토어 집합의 근사를 출력한다.

using System.Data;
using System.Text;

public class BaekJoon
{
    public  void Main()
    {
        
        int n = int.Parse(Console.ReadLine());
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < n; i++)
        {
            int s = int.Parse(Console.ReadLine());
            int z = 1;
            for (int j = 0; j < s; j++)
            {
                z *= 3;
            }
            List<int> numbers = new List<int>();
            Cantor(0,z);
            void Cantor(int start, int end)
            {
                if (start == end) numbers.Add(start);
                else
                {
                    Cantor(start, end / 3);
                    Cantor((end * 2) / 3, end);
                }
            }
            
            
        }

       
    }
   
}

 

이런 식으로 리스트에 담아서 해 보려 했지만, 사실은 더더욱 효율적인 코드가 있었다.

 

using System.Data;
using System.Text;

public class BaekJoon
{
    public static void Main()
    {
        StringBuilder sb = new StringBuilder();
        string input;
        while ((input = Console.ReadLine()) != null)
        {
            if (string.IsNullOrEmpty(input)) // 빈 줄 또는 null 입력시 종료
            {
                break;
            }
            int s = int.Parse(input);
            int N = (int)Math.Pow(3, s);
            char[] chars = new char[N];

            for (int j = 0; j < N; j++)
            {
                chars[j] = '-';
            }

            Cantor(chars, 0, N);

            sb.AppendLine(new string(chars));
        }

        Console.Write(sb);
    }

    public static void Cantor(char[] chars, int start, int end)
    {
        if (end - start < 2) return;
        int gap = end - start;
        int count = gap / 3;
        for (int i = start + count; i < end - count; i++)
        {
            chars[i] = ' ';
        }

        Cantor(chars, start, start + count );
        Cantor(chars, start + 2 * count , end);
    }
}

저기서  input = Console.ReadLin() ! = null 이 조건을 처음 사용해 보는데 무한 루프를 돌아서 

뭐가 문제인지 한참 찾았다. 백준이나 라이더 같은 IDE에서는 종료를 명시적으로 시켜줘야 한다는 것을 알게 되었다.