알고리즘/백준

[백준] 10816 C# 숫자 카드 2 (이분 탐색)

개발하는 디토 2022. 12. 5. 18:36

숫자 카드 2 

 

10816번: 숫자 카드 2

첫째 줄에 상근이가 가지고 있는 숫자 카드의 개수 N(1 ≤ N ≤ 500,000)이 주어진다. 둘째 줄에는 숫자 카드에 적혀있는 정수가 주어진다. 숫자 카드에 적혀있는 수는 -10,000,000보다 크거나 같고, 10,

www.acmicpc.net

문제

숫자 카드는 정수 하나가 적혀져 있는 카드이다. 상근이는 숫자 카드 N개를 가지고 있다. 정수 M개가 주어졌을 때, 이 수가 적혀있는 숫자 카드를 상근이가 몇 개 가지고 있는지 구하는 프로그램을 작성하시오.

입력

첫째 줄에 상근이가 가지고 있는 숫자 카드의 개수 N(1 ≤ N ≤ 500,000)이 주어진다. 둘째 줄에는 숫자 카드에 적혀있는 정수가 주어진다. 숫자 카드에 적혀있는 수는 -10,000,000보다 크거나 같고, 10,000,000보다 작거나 같다.

셋째 줄에는 M(1 ≤ M ≤ 500,000)이 주어진다. 넷째 줄에는 상근이가 몇 개 가지고 있는 숫자 카드인지 구해야 할 M개의 정수가 주어지며, 이 수는 공백으로 구분되어져 있다. 이 수도 -10,000,000보다 크거나 같고, 10,000,000보다 작거나 같다.

출력

첫째 줄에 입력으로 주어진 M개의 수에 대해서, 각 수가 적힌 숫자 카드를 상근이가 몇 개 가지고 있는지를 공백으로 구분해 출력한다.

예제 입력 1

10
6 3 2 10 10 10 -10 -10 7 3
8
10 9 -5 2 3 4 5 -10

예제 출력 1

3 0 0 1 2 0 0 2

 

내 풀이

예전에 Dictionary로 풀었던 문제인데 이분 탐색으로 푸는 방법을 알고 싶어서 다른 사람의 코드를 살펴보고 방법을 대강 탐색한 뒤 C#으로 풀어보았다.

 

참고한 코드

 

[백준/10816] 숫자 카드 2 (이분 탐색)

1. 문제 2. 접근 방법 이분 탐색 알고리즘으로 접근하되, 찾고자 하는 숫자가 시작하는 지점, 끝나는 지점을 찾아야 한다. 즉, 변형된 이분 탐색 알고리즘이라고 생각하면 된다. 찾고자 하는 숫자

kbw1101.tistory.com

 

  1. 입력으로 받은 첫번째 배열 nArr을 정렬해 검색하기 좋게 만들고, 검색 대상인 숫자가 시작되는 index와 마지막으로 등장하는 index를 찾기 위한 함수 2개를 만든다.
  2. 찾는 값이 nArr에서 존재할 때 가장 앞선 시작 index를 찾는다. 매번 중간 index인 mid를 계산해 찾는 숫자가
    • nArr[mid]보다 작거나 같으면 start ~ mid-1 사이의 범위를 다시 이분탐색한다.
    • nArr[mid]보다 크면 mid+1 ~ end 사이의 범위를 다시 이분탐색한다.
  3. 계속 이분탐색 하다가 start가 end보다 커지면 더 이상 start를 늘리는 것이 불가능하므로 검색을 종료하면서 적절한 값을 리턴한다.
    • nArr[start] == 찾는 값이면 찾는 값의 index인 start를 리턴
    • nArr[start] != 찾는 값이면 찾는 값이 없다고 판단하고 -1을 리턴
  4. 찾는 값이 nArr에서 존재할 때 가장 마지막 index를 찾는다. 매번 중간 index인 mid를 계산해 찾는 숫자가
    • nArr[mid]보다 크거나 같으면 mid+1 ~ end 사이의 범위를 다시 이분탐색한다.
    • nArr[mid]보다 작으면 start ~ mid-1 사이의 범위를 다시 이분탐색한다.
  5. 계속 이분탐색 하다가 start가 end보다 커지면 더는 start를 늘리는 것이 불가능하므로 검색을 종료하면서 적절한 값을 리턴한다.
    • nArr[end] == 찾는 값이면 찾는 값의 index인 end를 리턴
    • nArr[end] != 찾는 값이면 찾는 값이 없다고 판단하고 -1을 리턴

 

위의 설명을 읽고 나도 위와 같은 방식으로 풀었는데 IndexOutOfRange로 인해 계속 틀렸다. 무엇이 문제였을까?

using System;
using System.Text;
namespace Baekjoon
{
    class Program
    {
        static void Main(string[] args)
        {
            int n = int.Parse(Console.ReadLine());
            int[] nArr = Array.ConvertAll(Console.ReadLine().Split(' '), int.Parse);

            int m = int.Parse(Console.ReadLine());
            int[] mArr = Array.ConvertAll(Console.ReadLine().Split(' '), int.Parse);

            Array.Sort(nArr);
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < m; i++)
            {
                int first = SearchFirst(mArr[i], 0, n - 1);
                int last = SearchLast(mArr[i], 0, n - 1);
                int count = first == -1 ? 0 : last-first + 1;
                sb.Append($"{count} ");
            }
            Console.WriteLine(sb);

            int SearchFirst(int find, int start, int end)
            {
                //Console.WriteLine($"{start} {end} {(start + end) / 2}");

                int mid = (start + end) / 2;
                if(start > end)
                {
                    if (nArr[start] == find) return start;
                    else return -1;
                }

                if (nArr[mid] >= find) return SearchFirst(find, start, mid - 1);
                else return SearchFirst(find, mid + 1, end);
            }

            int SearchLast(int find, int start, int end)
            {   
                //Console.WriteLine($"{start} {end} {(start+end)/2}");

                int mid = (start + end) / 2;
                if (start > end)
                {
                    if (nArr[end] == find) return end;
                    else return -1;
                }

                if (nArr[mid] <= find) return SearchLast(find, mid + 1, end);
                else return SearchLast(find, start, mid - 1);
            }
        }
    }
}

 

반례

입력값이 아래와 같다고 해보자.

1
3
4
-1 0 2 5

주석 처리한 Console.WriteLine 부분을 주석 해제하고 로그를 찍어보면 해답이 보인다.

숫자를 검색하는 배열인 nArr의 길이가 1일 때, start나 end가 -1이 되는 경우가 생긴다. 그러면 IndexOutOfRange가 날 수밖에 없다. 아마 8개월 전에 테스트 케이스가 추가되면서 이러한 반례가 생긴 것 같다.

 

 

정답 풀이

start, end가 0보다 작아지거나 배열의 길이보다 같거나 커질 때는 index 범위를 벗어난 것이고, 이미 탐색 숫자가 nArr에 없다는 의미이므로 그냥 -1을 리턴하도록 했다.

using System;
using System.Text;
namespace Baekjoon
{
    class Program
    {
        static void Main(string[] args)
        {
            int n = int.Parse(Console.ReadLine());
            int[] nArr = Array.ConvertAll(Console.ReadLine().Split(' '), int.Parse);

            int m = int.Parse(Console.ReadLine());
            int[] mArr = Array.ConvertAll(Console.ReadLine().Split(' '), int.Parse);

            Array.Sort(nArr);
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < m; i++)
            {
                int first = SearchFirst(mArr[i], 0, n - 1);
                int last = SearchLast(mArr[i], 0, n - 1);
                int count = first == -1 ? 0 : last-first + 1;
                sb.Append($"{count} ");
            }
            Console.WriteLine(sb);

            int SearchFirst(int find, int start, int end)
            {
                if (start >= n || start < 0) return -1; 
                int mid = (start + end) / 2;
                if(start > end)
                {
                    if (nArr[start] == find) return start;
                    else return -1;
                }

                if (nArr[mid] >= find) return SearchFirst(find, start, mid - 1);
                else return SearchFirst(find, mid + 1, end);
            }

            int SearchLast(int find, int start, int end)
            {   
                if (end >= n || end < 0) return -1;

                int mid = (start + end) / 2;
                if (start > end)
                {
                    if (nArr[end] == find) return end;
                    else return -1;
                }

                if (nArr[mid] <= find) return SearchLast(find, mid + 1, end);
                else return SearchLast(find, start, mid - 1);
            }

        }
    }
}

 

 

 

감사의 글

반례를 찾아주신 heokiro님... 매우 감사드립니다. 갓키로...