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