포스트

백준 10815. 숫자 카드

백준 10815. 숫자 카드

숫자 카드

간단한 탐색 문제

1. 간단 설명


Image

숫자카드 N개를 가지고 있다

정수 M개가 주어졌을 때 이 수가 적혀있는 숫자를 갖고 있는지 아닌지 구하는 프로그램

  • N (1 ≤ N ≤ 500,000)
  • 각 숫자는 -10,000,000 보다 크거나 같고, 10,000,000보다 작거나 같다
  • M(1 ≤ M ≤ 500,000)
  • 넷째 줄 부터 M개는 상근이가 갖고있는지 구해야할 M개의 정수

2. 알고리즘


50만개의 숫자가 주어지고, 그 중에서 일치하는 숫자가 있는지 찾는 문제

최악의 케이스인 경우

상근이가 가진 숫자 카드가 50만개, 확인해야하는 카드의 숫자 카드가 50만개인 경우

브루트 포스 방식으로 계산한다면 각 M개의 숫자에 대해 N개의 숫자를 선형 탐색을 수행해야 한다

이는 약 2500억으로 2초의 시간제한을 초과한다

탐색 방법 중 시간복잡도가 효율적인 이진 탐색을 사용한다면

상근이가 가진 숫자카드를 정렬하는데 시간 복잡도 O(NlogN)

각 M개의 숫자에 대해 이진 탐색을 수행하는 시간복잡도 O(MlogN)으로

총 시간 복잡도는 O(NlogN + MlogN)이다

이는 약 19,000,000 정도로 2초 내에 풀이 가능

또 다른 방법으로는 해시맵 방식을 사용하면

숫자 카드를 해시맵에 저장하는데 O(N)

M개의 숫자에 대해 해시맵에 존재 여부 체크 O(M)

총 시간 복잡도는 O(N+M)으로 100만이므로 가장 효율적으로 동작한다

3. 소스코드


  • 이진 탐색을 이용한 방법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int N, M;
vector<int> v1, v2;

void Input() {
	freopen_s(new FILE*, "input.txt", "r", stdin);
	cin >> N;
	for (int i = 0; i < N; i++) {
		int tmp; cin >> tmp;
		v1.push_back(tmp);
	}
	cin >> M;
	for (int i = 0; i < M; i++) {
		int tmp; cin >> tmp;
		v2.push_back(tmp);
	}
}

bool MyBinarySearch(int target) {
	int start = 0, end = v1.size() - 1;

	while (start <= end) {
		int mid = (start + end) / 2;
		// 원하는 값을 찾은 경우
		if (target == v1[mid]) return true;

		// 중간값보다 목표가 작은 경우, 앞쪽을 검사
		if (target < v1[mid])
			end = mid - 1;
		// 중간값 보다 목표가 큰 경우, 뒷쪽을 검사
		else if (target > v1[mid])
			start = mid + 1;
	}
	// 끝까지 탐색했지만 값이 없는 경우 
	return false; 
}

int main() {
	Input();

	sort(v1.begin(), v1.end());
	
	for (int e : v2) {
		if (MyBinarySearch(e)) cout << "1 ";
		else cout << "0 ";
	}

	return 0;
}
  • 해시맵을 사용한 풀이 방법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <vector>
#include <unordered_set>

using namespace std;

int N, M;
unordered_set<int> cards;
vector<int> quaries;

void Input() {
	freopen_s(new FILE*, "input.txt", "r", stdin);
	cin >> N;
	for (int i = 0; i < N; i++) {
		int tmp; cin >> tmp;
		cards.insert(tmp);
	}
	cin >> M;
	for (int i = 0; i < M; i++) {
		int tmp; cin >> tmp;
		quaries.push_back(tmp);
	}
}

int main() {
	Input();

	for (int i = 0; i < M; i++) {
		if (cards.find(quaries[i]) != cards.end()) {
			cout << "1 ";
		}
		else {
			cout << "0 ";
		}
	}

	return 0;
}

Image

아래가 이진 탐색을 이용한 방법, 위가 해시맵을 사용한 방법이다

이진 탐색을 이용하는 방법은 메모리 사용량이 상대적으로 적다

그러나 정렬된 상태에서만 사용 가능하므로, 사전에 정렬이 필요하다

시간복잡도가 O(MlogN)이므로 M과 N이 매우 클 때, 해시맵보다 느릴 수 있다

또한 범위 검색도 가능함

해시맵 풀이 과정은 평균적으로 O(1)의 조회 시간을 가지므로 매우 빠르게 탐색할 수 있다

그리고, 라이브러리를 사용해서 쉽게 구현할 수 있으므로 insert 함수와 find로 삽입, 탐색을 수행할 수 있다

메모리 사용량이 벡터보다 많고, 충돌이 발생한다면 시간 복잡도가 증가하게 된다

즉 정리하자면

이진 탐색은 데이터를 정렬된 상태로 유지하면서 작업을 수행해야할 때, 메모리가 제한적일때 사용하고

해시맵은 특정값의 존재 여부만 확인할 때, 속도가 제일 중요할 때 사용하면 좋다

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.