풀이

여러 개의 전화번호 목록들이 있을 때, 한 전화번호가 다른 전화번호의 접두어와 똑같다면 false를 리턴하고, 아니라면 true를 리턴하면 된다.

 

전화번호의 목록의 크기가 최대 100만이므로, 2중 포문은 안될 것 같고, 더 빠른 방법을 찾던 도중에 정렬을 하면 편하게 비슷한 숫자들끼리 뭉쳐있어서 양 옆의 비교가 가능할 것 같았다.

 

전화번호의 목록들을 일단 정렬한 다음에 앞의 전화번호에서 뒤의 전화번호의 접두어만 비교해서 같으면 false를 리턴하게 하고, 그러한 부분이 없다면 true를 리턴하면 된다.

#include <string>
#include <vector>
#include <algorithm>
using namespace std;

bool solution(vector<string> phone_book) {
    sort(phone_book.begin(), phone_book.end());
    for(int i = 0; i < phone_book.size() - 1; i++) {
        string previous = phone_book[i];
        string current = phone_book[i+1].substr(0, previous.size());
        if(previous == current) 
            return false;
    }
    return true;
}

풀이

문제에 명시된 대로 (x1, y1) ~ (x2, y2) 범위를 시계방향으로 회전시킨 후, 회전된 값들 중에서 가장 작은 값을 찾아내는 문제다. 단순 빡구현 문제.

처음에 문제를 보자마자 살짝 겁을 먹었으나, 하나하나 천천히 살펴보면서 구현하니까 생각보다 어렵지 않은 문제였다.

 

어떻게 하면 시계방향으로 돌릴 수 있을까?

하나의 큰 직사각형 혹은 정사각형을 4 부위로 나눠서 옮기기로 생각했다.

 

1. 왼쪽에 있는 사각형 옮기기

가장 왼쪽에 있는 사각형 부터 한 칸씩 위로 올려주면 된다. 이때 주의할 점이 있는데 가장 처음 지점 [x1][y1]을 temp라는 임시공간에 저장해둔다. 이러한 이유는 있다가 살펴보고 우선 한 칸씩 이동한다는 점에서 생각하는 것이다.

(2, 2) <- (3, 2) 로

(3, 2) <- (4, 2) 로

(4, 2) <- (5, 2) 로

규칙을 보면, (row, column)에서 column은 y1으로 고정되어 있고, row 값만 변동되는 것을 알 수 있다.

for(int i = x1; i < x2; i++) {
	board[i][y1] = board[i+1][y1];
}

2. 아래쪽에 있는 사각형 옮기기

한 번만 더 생각해보자. 빨간색 화살표는 아래 지점의 사각형들을 한 칸씩 옮기는 과정이다.

(5, 2) <- (5, 3)

(5, 3) <- (5, 4)

규칙을 찾았는가? row가 x2로 고정되어 있고, column만 변동하고 있다.

for(int i = y1; i < y2; i++) {
   board[x2][i] = board[x2][i+1];
}

3. 오른쪽에 있는 사각형 옮기기

살짝의 순서만 바뀌고 규칙만 찾으면 쉽게 해결할 수 있다.

for(int i = x2; i > x1; i--) {
   board[i][y2] = board[i-1][y2];
}

4. 위쪽에 있는 사각형 옮기기

for(int i = y2; i > y1; i--) {
   board[x1][i] = board[x1][i-1];
}

아까 저장했던 temp 임시 공간에 저장된 수는 다 옮기고 나서 마지막에 넣어준다.

이유는 한 칸씩 밀렸을 때 board [x1][y1+1] 은 board [x1][y1]으로 덮혀씌워지게 되는데 처음에 왼쪽 사각형부터 옮겼으므로 board[x1][y1] 값은 이미 board[x1+1][y1] 값으로 갱신된다. 따라서 기존에 임시 공간에 저장해둔 board[x1][y1] 수를 board [x1][y1+1]에 넣어주는 것이다.

board[x1][y1+1] = temp;

이렇게 행렬을 시계방향으로 옮기고 나서, 문제에서 요구한 가장 작은 숫자를 찾는 방법은 어떻게 할까?

처음에 temp의 값을 변수에 넣어주고, 이후에 회전을 할 때마다 그 값과 비교해서 min 값을 갱신해주면 된다.

#include <string>
#include <vector>
#include <iostream>
using namespace std;

vector<int> solution(int rows, int columns, vector<vector<int>> queries) {
    vector<vector<int> > board(rows + 1, vector<int>(columns + 1));
    for(int i = 1; i <= rows; i++) {
        for(int j = 1; j <= columns; j++) {
            board[i][j] = ((i-1) * columns + j);
        }
    }
    
    vector<int> answer;
    for(int i = 0; i < queries.size(); i++) {
        int x1 = queries[i][0];
        int y1 = queries[i][1];
        int x2 = queries[i][2];
        int y2 = queries[i][3];
        int temp = board[x1][y1];
        int num = temp;
        
        for(int i = x1; i < x2; i++) {
            board[i][y1] = board[i+1][y1];
            num = min(num, board[i][y1]);
        }
        
        for(int i = y1; i < y2; i++) {
            board[x2][i] = board[x2][i+1];
            num = min(num, board[x2][i]);
        }
        
        for(int i = x2; i > x1; i--) {
            board[i][y2] = board[i-1][y2];
            num = min(num, board[i][y2]);
        }
        
        for(int i = y2; i > y1; i--) {
            board[x1][i] = board[x1][i-1];
            num = min(num, board[x1][i]);
        }
        
        board[x1][y1+1] = temp;
        
        answer.push_back(num);
    }
    return answer;
}

풀이

문제의 지문이 엄청 길지만 요약하면 다음과 같다.

두 문자열이 주어지는데, 두 문자열에서 교집합과 합집합을 구한 다음에 교집합 / 합집합의 결과를 65536으로 곱한 값을 리턴하면 된다.

 

하나씩 확인해보자.

1. 문자열에서 영문자로 된 글자 쌍만 유효하며, 아래와 2글자씩 문자열을 쪼개서 넣어놔야 한다.

어떻게 쪼갤 수 있을까?

string의 substr을 이용해서 간단하게 쪼갤 수 있다. 또한 영문자가 아니라면 무시하자.

또 문제에서 보면, "AB" = "Ab" = "ab" 모두 같은 문자로 취급하므로 다 소문자로 만들어서 넣어준다.

vector<string> a;
vector<string> b;
for (int i = 0; i < str1.size() - 1; i++) {
    string s = str1.substr(i, 2);
    if (isalpha(s[0]) && isalpha(s[1])) {
        s[0] = tolower(s[0]);
        s[1] = tolower(s[1]);
        a.push_back(s);
    }
}

for (int i = 0; i < str2.size() - 1; i++) {
    string s = str2.substr(i, 2);
    if (isalpha(s[0]) && isalpha(s[1])) {
        s[0] = tolower(s[0]);
        s[1] = tolower(s[1]);
        b.push_back(s);
    }
}

2. 쪼개는 놨는데, 합집합과 교집합은 어떻게 구할까?

주의할 점이 있는데, 교집합을 구할 때 a의 string과 b의 string이 같다면 단순히 카운트를 하면 안 되고 check 배열을 각각 만들어서 서로 교집합을 체크했다면 true로 체크하면서 중복 여부를 피해 줘야 한다.

int t1 = 0, t2 = 0; // 교집합 수, 합집합 수
vector<bool> a_check(a.size(), false);
vector<bool> b_check(b.size(), false);
for (int i = 0; i < a.size(); i++) {
    for (int j = 0; j < b.size(); j++) {
        if (a[i] == b[j] && !a_check[i] && !b_check[j]) {
            a_check[i] = true;
            b_check[j] = true;
            t1++;
        }
    }
}

합집합은 간단하다. a와 b의 크기를 더한 다음에 교집합의 크기를 빼면 그것이 바로 합집합의 크기다.

t2 = a.size() + b.size() - t1;

만약에 교집합과 합집합이 0이라면 문제에서 바로 65536을 리턴하라고 했으므로 65536을 리턴하고,

아니라면 주어진 공식대로 65536 곱한 다음 소수점을 제외한 값을 리턴하면 된다.

t2 = a.size() + b.size() - t1;
if (a.size() == 0 && b.size() == 0) return 65536;
return (int)((double)t1 / t2 * 65536);

전체 소스 코드는 다음과 같다.

#include <string>
#include <vector>
#include <iostream>
using namespace std;

int solution(string str1, string str2) {
    vector<string> a;
    vector<string> b;
    for (int i = 0; i < str1.size() - 1; i++) {
        string s = str1.substr(i, 2);
        if (isalpha(s[0]) && isalpha(s[1])) {
            s[0] = tolower(s[0]);
            s[1] = tolower(s[1]);
            a.push_back(s);
        }
    }

    for (int i = 0; i < str2.size() - 1; i++) {
        string s = str2.substr(i, 2);
        if (isalpha(s[0]) && isalpha(s[1])) {
            s[0] = tolower(s[0]);
            s[1] = tolower(s[1]);
            b.push_back(s);
        }
    }

    int t1 = 0, t2 = 0;
    vector<bool> a_check(a.size(), false);
    vector<bool> b_check(b.size(), false);
    for (int i = 0; i < a.size(); i++) {
        for (int j = 0; j < b.size(); j++) {
            if (a[i] == b[j] && !a_check[i] && !b_check[j]) {
                a_check[i] = true;
                b_check[j] = true;
                t1++;
            }
        }
    }

    t2 = a.size() + b.size() - t1;
    if (a.size() == 0 && b.size() == 0) return 65536;
    return (int)((double)t1 / t2 * 65536);
}

풀이

N X N 체스판 위에 N개의 퀸을 서로 공격할 수 없게 놓는 문제다.

단순하게 생각하면 N^N^2(true, false) 모든 경우의 수를 비교해서 찾을 수 있지만, N의 크기가 커질수록 연산량이

감당할 수 없는 수준으로 된다. 따라서 가지치기(Pruning)를 하면서 문제를 해결해야 한다.

 

가치지기란?

어떤 강을 건너기 위해 돌다리를 두들겨보듯이 그 돌이 안전한지 안한지, 유망했는지 아닌지를 검사해서 유망하면 건너가고 아니라면 다시 돌아와서 다른 길을 선택하는 방법이다.

기존의 DFS처럼 주먹구구식으로 끝까지 가보는 것은 어떤 문제에서는 좋지만 이 문제에서는 연산량이 감당이 되지 않기 때문에 DFS처럼 탐색은 하지만 유망한 지 안 한 지를 검사해서 탐색의 범위를 좁혀가면서 문제를 해결한다.

 

N-Queen에서는 가지치기는 내가 어떠한 행(row)에 0 ~ N 중 하나의 열(col)에 Queen을 둔다고 가정했을 때,

다음 행(row + 1)에 Queen을 두기 전에 내가 두었던 Queen이 기존 [0 ~ row-1]까지 Queen들과 서로 공격하지 않는 범위에 두었는지를 체크하는 방법이다.

 

위에 그림처럼

row = 2인 행에 0번째 열에 Queen을 배치했다고 생각하자.

그러면 지금 둔 Queen이 적절한 배치인지를 확인하기 위해서 0번째 행에 배치한 Queen, 1번째 행에 배치한 Queen과의 충돌이 일어나지 않는지를 체크하면 된다.

 

체크할 때는 같은 행에 있는지 체크는 배제하면 된다. 이유는 해당 행에는 한 개의 Queen만 배치하기 때문이다.

체크하는 코드를 보자. 우리가 유망한지 체크하는 방법은 같은 열에 걸리거나 대각선에 걸리면 그것은 유망하지 않다.

이럴 경우에는 다시 해당 row에 Queen을 배치하지 않고 다른 열에다가 배치하고 유망한 지를 검사해보고,

만약에 유망하다면 row + 1에 Queen을 배치하러 가면 된다.

int col[15]; // col[i] = j : (i,j)에 Queen을 배치

bool isPromising(int row)
{
	// [0 ~ row) 단계 같은 열, 대각선 체크
	for (int i = 0; i < row; i++)
	{
		if (col[i] == col[row]) return false;
		if (abs(i - row) == abs(col[i] - col[row])) return false;
	}

	return true;
}

그렇다면 언제까지 Queen을 탐색할까?

row가 N까지 Queen을 잘 배치했다면, N x N 체스판 위에 N개의 Queen을 서로 공격할 수 없게 배치한 것이므로 이것은 문제에서 구하는 정답의 경우의 수 중 하나이기 때문에 개수를 하나 카운트하면 된다.

즉, row가 N이면 정답을 +1 증가시켜준다.

// row행 까지는 퀸을 놓은 상태,
void dfs(int row)
{
	if (row == n)
	{
		ans++;
		return;
	}
	// row행에 퀸을 배치하고 유망한지 체크
	// 유망하다면 다음 행에 퀸을 배치하러 가고,
	// 유망하지 않다면 다음 열에 다시 퀸을 배치하고 유망한지 검사한다.
	for (int i = 0; i < n; i++)
	{
		col[row] = i;
		if (isPromising(row))
			dfs(row + 1);
	}
}

 

정리하면,

각 단계(행)에서 어떤 열에 Queen을 둘지 정하고, Queen을 배치했다면 이전 단계에서 배치했던 모든 Queen들과 서로 공격하지 않는지를 검사해서 유망하다면 다음 단계로 넘어가고, 아니라면 그 단계에서 다음 열에 다시 Queen을 배치하는 방식으로 진행한다. 이렇게 하면 기존 DFS 탐색보다 빠르게 유망한 지 안 한 지 가지치기를 통해 더 빠르게 답을 구할 수 있을뿐더러 끝까지 탐색하지 않아도 된다는 장점이 있다.

 

전체 소스코드는 다음과 같다.

#include <iostream>
#include <cmath>
using namespace std;

int n, ans;
int col[15]; // col[i] = j : (i,j)에 Queen을 배치

bool isPromising(int row)
{
	// [0 ~ row) 단계 같은 열, 대각선 체크
	for (int i = 0; i < row; i++)
	{
		if (col[i] == col[row]) return false;
		if (abs(i - row) == abs(col[i] - col[row])) return false;
	}

	return true;
}

// row행 까지는 퀸을 놓은 상태,
void dfs(int row)
{
	if (row == n)
	{
		ans++;
		return;
	}
	// row행에 퀸을 배치하고 유망한지 체크
	// 유망하다면 다음 행에 퀸을 배치하러 가고,
	// 유망하지 않다면 다음 열에 다시 퀸을 배치하고 유망한지 검사한다.
	for (int i = 0; i < n; i++)
	{
		col[row] = i;
		if (isPromising(row))
			dfs(row + 1);
	}
}

int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);
	cin >> n;
	dfs(0);
	cout << ans << '\n';
	return 0;
}

풀이

1. 인쇄 대기목록의 가장 앞에 있는 문서 J를 대기 목록에서 꺼낸다.
2. 나머지 인쇄 대기목록에서 J보다 중요도가 높은 문서가 있다면 J를 대기 목록에서 가장 마지막으로 넣는다.
3. 그렇지 않으면 J문서를 인쇄한다.

앞에서 빼고, 뒤에서 다시 넣고 하는 자료구조는 Deque가 있다.

현재 가장 앞에 있는 문서의 중요도가 뒤에 있는 모든 중요도보다 높거나 같다면 그 문서를 출력하고,

아니라면 현재 가장 앞에 있는 문서를 뽑아서 뒤에 다시 넣는다.

 

뒤에 있는 모든 중요도 중에서 가장 높은 중요도는 어떻게 뽑을까?

간단하게 for문을 돌려 뽑을 수도 있지만 *max_element를 이용하면 쉽게 뽑아올 수 있다.

다만 시간 복잡도는 for문과 동일하지만, 간단하게 코드를 짤 수 있어서 편하고 용이하다.

#include <string>
#include <vector>
#include <deque>
#include <algorithm>
using namespace std;

int solution(vector<int> priorities, int location) {
    int answer = 0;
    deque<pair<int, int> > dq;
    for (int i = 0; i < priorities.size(); i++) {
        dq.push_back({ priorities[i], i });
    }

    while (!dq.empty()) {
        int current_priority = dq.front().first;
        int current_location = dq.front().second;
        dq.pop_front();
        int max_priority = (*max_element(dq.begin(), dq.end())).first;
        if (current_priority >= max_priority) {
            answer++;
            if (current_location == location) return answer;
        }
        else {
            dq.push_back({ current_priority, current_location });
        }
    }

    return answer;
}

파라메트릭 서치

생각보다 받아들이는데 어려운 개념이다.

이 알고리즘은 최적화 문제결정 문제로 바꾸는 기법이다.

조건을 만족하면서도 최대한 많이 가져갈 수 있는 방법을 찾는 느낌?? 혹은

조건을 만족하면서도 최대한 적게 가져갈 수 있는 방법을 찾는 느낌이라고 생각하면 좋다.

 

예시를 들어보면 아래의 문제를 인용해서 생각해보자.

BOJ2110 공유기 설치

대표적인 파라메트릭 서치 문제다.

공유기 C개를 설치해야 하는데 두 공유기 사이의 거리를 최대한으로 하려면 어느 정도쯤에 설치해야 할까?라는 문제다.

 

(아래의 예시는 위의 문제 스포가 될 수 있어서 임의로 그냥 생각한 풀이입니다.)

예를 들어보자.

공유기 3개를 설치해야 하는데

만약에 내가 두 공유기 사이의 거리를 2로 설치한다고 가정했을 때,

2씩 공유기를 설치해보니 공유기 3개가 모두 설치되었다. 이는 조건을 만족하니까 우리가 찾는 정답이므로 2로 갱신한다.

 

그렇다면, 여기서 끝일까? 아니다.

한 발자국 더 나아가 보면서 생각해 봐야 한다.

두 공유기 사이의 거리를 3으로 확장해보면 어떨까?

3씩 공유기를 설치해보니 공유기 3개가 모두 설치되었다. 분명 2일 때도 공유기 3개가 모두 설치되었는데? 조금 더 멀리 설치해도 공유기 3개가 모두 설치된 것이다. 마찬가지로 조건을 만족하니까 기존에 저장했던 2를 3으로 갱신한다.

 

그렇다면, 진짜로 끝일까? 아니다.

한 발자국 더 나아가 보자.

두 공유기 사이의 거리를 4로 확장해보면?

4씩 공유기를 설치해보니 공유기 2개가 설치되고 1개는 설치되지 못했다.

이렇게 된다면 주어진 조건 공유기 3개를 설치하지 못했으므로 정답이 아니다.

따라서 여기서 탐색을 멈춘다. 즉, 정답에 저장된 3이 답이 된다.

 

이렇듯, 범위를 좁혀가면서 주어진 조건을 만족하면 한 걸음 더 나아가 보고 아니라면 돌아오는 느낌으로 탐색을 하면 된다.

 

이런 기법은 많은 문제를 풀면 느낌이 오게 된다.

아래의 문제들은 백준 사이트에서 파라메트릭 서치 대표 문제들이다.

시간 나면 다시 풀어보자.

랜선 자르기

기타 레슨

공유기 설치

나무 자르기

 

기본적인 구현은 다음과 같이 생각하면 좋다.

1. 초깃값 세팅 start, end

2. mid가 결정된 후 그 mid가 조건에 맞는지 검사하기

3. 조건에 따라 범위 수정 후에 다시 2번으로 돌아가기

int start = 0; // 문제마다 start와 end 값은 변동될 수 있다.
int end = 1e8; // 범위가 커진다면 long long 자료형으로 오버플로우를 방지할 수 있다.
int ans = 0;
while(start <= end)
{
    int mid = (start + end) / 2;
    if(calc(mid)) // 조건을 만족한다면
    {
    	ans = mid;
        s = mid + 1; // 한 걸음 더 가보자.
    }
    else
    	e = mid - 1; // 조건을 만족하지 못하니까, 탐색 범위를 줄여보자.
}

또, 파라메트릭 서치 같은 문제들은 범위가 어마어마하게 크다. 보통 O(N)으로 해결될 수 없는 문제들 같은 경우 이러한 방법도 생각해보는 것도 좋다.

'Algorithm > Algorithm Concept' 카테고리의 다른 글

수학 관련  (0) 2021.08.18
백트래킹 (Backtracking)  (0) 2021.07.28
[cmath] math 관련 함수들  (0) 2021.05.07
[GCD, LCM] 유클리드 호제법  (0) 2021.05.07
stable sort과 unstable sort  (0) 2021.04.26

cmath

cmath에는 c++에서 각종 수학 함수들을 가지고 있다.

신기하게도 min, max는 algorithm에 구현되어있다는 것을 명심하자.

제곱 구하기

#include <cmath>

// C++ (함수 오버로딩 = 함수 중복 정의)
double pow(double x, double y);
float pow(float x, float y);
float pow(float x, int y);
long double pow(long double x, long double y);
long double pow(long double x, int y);

C++에서는 함수의 이름을 pow로 통일해서 사용하면 된다.

 

제곱근 구하기

#include <cmath>

double sqrt(double x);
float sqrt(float x);
long double sqrt(long double x);

 

올림

#include <cmath>

double ceil(double x);
float ceil(float x);
long double ceil(long double x);

'Algorithm > Algorithm Concept' 카테고리의 다른 글

수학 관련  (0) 2021.08.18
백트래킹 (Backtracking)  (0) 2021.07.28
파라메트릭서치 (Parametric Search)  (0) 2021.05.09
[GCD, LCM] 유클리드 호제법  (0) 2021.05.07
stable sort과 unstable sort  (0) 2021.04.26

유클리드 호제법?

두 수의 최대 공약수를 O(lgN)만에 구하는 방법이다.

생각보다 자주 까먹으니까.. 틈틈이 시간 나면 한 번씩 봐주자.

재귀 함수 구현

int gcd(int a, int b)
{
    if(b == 0) return a;
    else return gcd(b, a % b);
}

반복문 구현

전제 조건 : a > b
while(1)
{
    int r = a % b;
    if(r == 0) return b;
    
    a = b;
    b = r;
}

최소 공배수는?

int lcm(int a, int b)
{
    return (a / gcd(a, b)) * (b / gcd(a, b)) * gcd(a, b);
}

최소 공배수에서 gcd(a, b)를 3번이나 해주는 이유는 특정 구간에서 오버플로우의 방지를 막기 위해서이다.

'Algorithm > Algorithm Concept' 카테고리의 다른 글

수학 관련  (0) 2021.08.18
백트래킹 (Backtracking)  (0) 2021.07.28
파라메트릭서치 (Parametric Search)  (0) 2021.05.09
[cmath] math 관련 함수들  (0) 2021.05.07
stable sort과 unstable sort  (0) 2021.04.26

풀이

처음에는 이 문제를 보고 어떤 자료구조를 사용해야 빠르게 해결할 수 있을까?를 많이 고민한 문제.

문제에서 2, 5, 8, 0을 누를 때는 왼쪽 엄지랑 오른쪽 엄지중 더 가까운 곳에 있는 엄지가 먼저 누르기 때문에

좌표를 사용해야겠다는 생각이 떠올랐다.

 

좌표 (행,열) 기준!

1번 키패드의 좌표는 (0, 0)

2번 키패드의 좌표는 (0, 1)

0번 키패드의 좌표는 (3, 1)

~번 키패드의 좌표를 표시해주는 자료구조 중 map을 사용하면 편할 거 같아서 map을 채택하면 쉽게 구현할 수 있다.

이렇게 각 키패드 번호마다 좌표를 부여해서, 해당 키패드를 누를 때 왼쪽 엄지랑 오른쪽 엄지의 거리를 구해서 더 작은 거리를 가진 엄지가 먼저 누르도록 구현하면 된다.

#include <string>
#include <vector>
#include <cmath>
#include <map>
#include <iostream>
using namespace std;

map<char, pair<int,int>> m;

void init() {
    m['1'] = pair<int,int>(0,0);
    m['2'] = pair<int,int>(0,1);
    m['3'] = pair<int,int>(0,2);
    m['4'] = pair<int,int>(1,0);
    m['5'] = pair<int,int>(1,1);
    m['6'] = pair<int,int>(1,2);
    m['7'] = pair<int,int>(2,0);
    m['8'] = pair<int,int>(2,1);
    m['9'] = pair<int,int>(2,2);
    m['*'] = pair<int,int>(3,0);
    m['0'] = pair<int,int>(3,1);
    m['#'] = pair<int,int>(3,2);
}

string solution(vector<int> numbers, string hand) {
    string answer = "";
    
    char left = '*';
    char right = '#';
    init();
    
    for(int i = 0; i < numbers.size(); i++) {
        int n = numbers[i];
        
        if(n == 1 || n == 4 || n == 7) answer += "L";
        else if(n == 3 || n == 6 || n == 9) answer += "R";
        else {
            int left_distance = abs(m[left].first - m[n + '0'].first) + abs(m[left].second - m[n + '0'].second);
            int right_distance = abs(m[right].first - m[n + '0'].first) + abs(m[right].second - m[n + '0'].second);
            if(left_distance == right_distance) {
                if(hand == "left")
                    answer += "L";
                else
                    answer += "R";
            }
            else if(left_distance < right_distance) answer += "L";
            else answer += "R";
        }
        
        if(answer.back() == 'R')
            right = n + '0';
        else
            left = n + '0';
    }
    
    return answer;
}

풀이

문제 요약.

1. 단 한 명의 선수를 제외하고 나머지 모든 선수들은 완주를 했다.

2. 선수들의 수는 10만

 

간단하게 생각하면 참가자와 완주자를 2중 포문으로 모두 대조해서 찾을 수 있지만

10만 X 10만은 시간 초과가 발생할 수 있다.

따라서 더 간단하게 대조하는 방법은 map 자료구조를 사용하는 것이다.

map의 특징 중 하나인 검색의 속도가 O(lgN)을 보장하기 때문에 N이 10만이 들어온다 하더라도

무리 없이 해결할 수 있다.

 

우선 참가자들을 순회하면서 해당 선수 이름의 횟수를 늘려준다.

그리고 완주자들을 순회하면서 해당 선수 이름의 횟수를 빼준다.

만약에 해당 선수의 이름을 조회하였을 때 그 선수의 값이 0이 아닌 다른 숫자라면?

그 선수는 완주하지 못한 이름이다. 따라서 그 이름을 리턴하면 끝.

#include <string>
#include <vector>
#include <map>
using namespace std;

string solution(vector<string> participant, vector<string> completion) {
    string answer = "";
    
    map<string, int> m;
    for(int i = 0; i < participant.size(); i++) {
        string name = participant[i];
        m[name]++;
    }
    
    for(int i = 0; i < completion.size(); i++) {
        string name = completion[i];
        m[name]--;
    }
    
    for(auto p : m) {
        if(p.second > 0) {
            answer = p.first;
            break;
        }
    }
    
    return answer;
}

+ Recent posts