1. 문제

1) 문제 링크

    : https://programmers.co.kr/learn/courses/30/lessons/17677

 

코딩테스트 연습 - [1차] 뉴스 클러스터링

뉴스 클러스터링 여러 언론사에서 쏟아지는 뉴스, 특히 속보성 뉴스를 보면 비슷비슷한 제목의 기사가 많아 정작 필요한 기사를 찾기가 어렵다. Daum 뉴스의 개발 업무를 맡게 된 신입사원 튜브

programmers.co.kr

 

2) 설명

자카드 유사도를 활용해 두 문자열의 유사도를 비교하는 문제다

자카드 유사도에 대한 자세한 설명은 문제 링크에 있다

 

2. 풀이

1) 알고리즘

    : 집합?

 

2) 풀이 설명

C++로 sstream 사용해서 풀어도 되긴 하는데 파이썬으로 풀기에 너무 적합한 문제 같아서 그냥 파이썬으로 풀어봤다.

일단 소문자 대문자를 가리지 않기 때문에 문자열을 다 소문자로 변환했다.

그리고 각 문자열을 두 글자씩 떼서 리스트를 만든다. 이때 특수문자가 포함되어 있으면 건너뛴다.

이제 n(교집합) / n(합집합) 을 구하면 된다.(마지막에 한 번만 곱해주면 되기 때문에 65536을 곱하는 건 일단 생략하겠다...)

n(A U B) = n(A) + n(B) - n(A ^ B) // n(A ^ B)는 A B 교집합

공식을 이용한다.

교집합을 구하는 건 str1_arr(str1에서 두 글자씩 떼어낸 문자열 집합)에 있는 문자열 중 str2_arr(str2에서 두 글자씩 떼어낸 문자열 집합)에 있는 것들만 카운팅해준다. 카운팅하면 str2_arr에서 그 문자열은 빼줘야 한다. 왜냐하면 다음과 같은 경우를 방지하기 위해서다.

str1_arr = {1, 1, 1}

str2_arr = {1, 2, 3}

일 때 처음 1을 카운팅해주고 str2_arr에서 빼주지 않으면 두 번째, 세 번째 1에서도 str2_arr에 1 값이 있기 때문에 교집합의 개수가 3이 되기 때문이다.

 

여튼 그렇게 교집합의 개수를 구했으면 공식 따라서 자카드 유사도를 구해주면 된다.

 

 

3. 코드

더보기
def solution(str1, str2):
    mul_val = 65536
    
    # 소문자 변환
    str1 = str1.lower()
    str2 = str2.lower()
    
    # 두글자씩 떼어서 배열로 변환
    str1_arr = [ str1[i : i + 2] for i in range(len(str1) - 1) if str1[i : i + 2].isalpha() ]
    str2_arr = [ str2[i : i + 2] for i in range(len(str2) - 1) if str2[i : i + 2].isalpha() ]
    
    union_count = len(str1_arr) + len(str2_arr) # (A U B) = (A) + (B) - (A ^ B) 공식 사용 : 교집합 개수 구한 후 빼줘야 함
    # 교집합 개수 구하기
    intersection_count = 0
    for x in str1_arr:
        if x in str2_arr:
            intersection_count += 1
            str2_arr.remove(x)
    
    # 합집합 개수 구하기
    union_count -= intersection_count # 교집합 개수 빼주기
    
    if union_count == 0:
        return mul_val
    else:
        return int(intersection_count / union_count * mul_val)

 

 

4. 느낀점

리스트 컴프리헨션 최고!

문자열에 isalpha()라는 함수가 있다는 걸 처음 알았다..

Counter라는 걸 사용하면 교집합 합집합을 더 쉽게 구할 수 있는 것  같다. 틈 나면 공부해보자!(보통 이러면 안 한다)

'알고리즘 > 프로그래머스' 카테고리의 다른 글

[LV4] 4단 고음  (0) 2021.08.19
[LV3] 불량 사용자  (0) 2021.08.13
[LV4] 호텔 방 배정  (0) 2021.08.13
[LV2] 거리두기 확인하기  (0) 2021.07.29

1. 문제

1) 문제 링크

    : https://www.acmicpc.net/problem/1727

 

1727번: 커플 만들기

첫째 줄에 n, m(1≤n, m≤1,000)이 주어진다. 다음 줄에는 n명의 남자들의 성격이 주어진다. 그 다음 줄에는 m명의 여자들의 성격이 주어진다. 성격은 1,000,000이하의 자연수이다.

www.acmicpc.net

 

2) 설명

남자 n명, 여자 m명이 있고, 각각 점수가 매겨져 있을 때,

조건 1. 남자와 여자를 최대한 매칭시키고

조건 2. 그때 커플이 된 남녀 점수차의 합을 최소화

하는 문제다.

난이도 : Gold 3

 

2. 풀이

1) 알고리즘

    :  dp

 

2) 풀이 설명

처음에 top-down 방식으로 풀려다가 개고생했다..

뭔가 코드도 더럽고... 틀리기도 계속 틀리고...

결국 다른 사람들의 풀이를 참고했다.

 

위의 조건 1에서 남자와 여자를 최대한으로 매칭시킨다는 말은 남자 무리와 여자 무리 중 인원이 적은 쪽은 반드시 매칭시켜야 한다는 의미다.

예를 들어 남자 3명 여자 6명 있으면 커플은 최대 3쌍까지만 매칭될 수 있다. 일단 커플을 최대한 매칭시켜야 하기 때문에 무조건 세 쌍까지 매칭시켜야 한다.

 

그 이후로는 어떻게 매칭시키냐의 문제다.

일단 남자와 여자를 점수 순서대로 정렬해준다.

그 다음에는 커플들의 점수차의 합을 최소화해야하기 때문에 알맞은 점화식이 필요하다.

 

dp[x][y] 을 "남자 x명, 여자 y명이 있을 때 만들어진 커플들의 점수차 합의 최소값" 이라고 정의해보자.

말이 긴데 결국 남자 x, 여자 y명일 때 위의 조건1, 2를 만족시키는 값이라는 말이다.

 

이 dp[x][y]의 값은 3가지 경우로 나눠서 구할 수 있다.

1. x == y 인 경우

  x == y라면 남자와 여자의 수가 같다는 말이다. 그러면 조건 1에 따라(최대한 커플을 매칭시켜야하므로)

  dp[x][y] = dp[x - 1][y - 1] + abs(male[x] - female[y])

  가 된다.

2. x > y인 경우

  x > y는 남자가 여자보다 많다는 말이다. 그러므로 여자는 반드시 모두 커플이 돼야 하고 남자는 여자의 수만큼만 커플이 되면 된다.

  이때 x번째 남자는 y번째 여자와 커플이 될 수도 있고 안 될 수도 있다.(이게 x > y일 때 모든 경우의 수다)

  핵심은 x번째 남자가 y번째 여자와 커플이 안 된다고 y번 이전의 여자와 커플이 될 수는 없다는 것이다.

  왜냐하면 여자의 수가 적기 때문에 여자는 반드시 커플을 맺어야 하는데, 만약 x번째 남자가 y - 1번째 여자랑 짝을 맺으면 y번째 여자는 짝을 못 맺기 때문이다! 이는 조건 1에 맞지 않는다!

  그러므로

  dp[x][y] = min(dp[x - 1][y - 1] + abs(male[x] - female[y]) // x번 남자와 y번 여자가 커플이 되는 경우

                      , dp[x - 1][y])                                     // x번 남자와 y번 여자가 커플이 안 되는 경우

  가 된다.

3. x < y 인 경우

  이건 2번과 반대의 경우기 때문에 설명은 생략한다.

  dp[x][y] = min(dp[x - 1][y - 1] + abs(male[x] - female[y]) // x번 남자와 y번 여자가 커플이 되는 경우

                      , dp[x][y - 1])                                     // x번 남자와 y번 여자가 커플이 안 되는 경우

 

이 점화식은 남녀가 점수 순서대로 정렬되어 있다는 전제 하에 올바르게 작동한다.

만약

남자 : 10 5 1

여자 : 2  5

이런 경우에는(정렬이 안 되어있는) dp 배열이

0 0 0 

0 8 5

0 3 8

0 1 7

이런 식으로 나오게 된다. 

그럼 dp[3][2] = 7이 되니까 틀린 답이다. (제대로 된 답은 1이 나와야 한다)

이 알고리즘으로 이중 반복문을 돌고나면 결국 dp[n][m]값이 우리가 구하는 답이 된다.

3. 코드

더보기
#include <iostream>
#include <vector>
#include <math.h>
#include <algorithm>

using namespace std;

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int n, m;
	cin >> n >> m;
	vector<int> male(n + 1, 0);
	vector<int> female(m + 1, 0);
	vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));

	sort(male.begin(), male.end());
	sort(female.begin(), female.end());

	for(int i=1;i<=n;i++)
		cin >> male[i];
	for(int i=1;i<=m;i++)
		cin >> female[i];

	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(i == j){
				dp[i][j] = dp[i - 1][j - 1] + abs(male[i] - female[j]);
			} else if(i > j){
				dp[i][j] = min(dp[i - 1][j - 1] + abs(male[i] - female[j]), dp[i - 1][j]);
			} else {
				dp[i][j] = min(dp[i - 1][j - 1] + abs(male[i] - female[j]), dp[i][j - 1]);
			}
		}
	}

	cout << dp[n][m] << endl;

	return 0;
}

 

 

4. 느낀점

dp에서는 탑다운 방식이 직관적이라고 생각해서 항상 탑다운으로 먼저 시도를 하는 편이다..

근데 약간 탑다운 풀이에 한계를 느낀 것 같다..

아마도 계속 잡고 있으면 탑다운으로도 맞힐 수 있을 것 같기는 한데

바텀업 풀이의 아이디어를 보면 이렇게 복잡하게 짜고 있는 탑다운 코드가 너무 야속해진다..

바텀업.. 연습하자...

'알고리즘 > 백준' 카테고리의 다른 글

[백준/2281] 데스노트(1차원 dp)  (2) 2021.10.01
[백준/7512] 연속하는 소수의 합  (0) 2021.09.20
[백준/2110] 공유기 설치  (0) 2021.08.01
[BOJ/1034] 램  (0) 2021.07.27
[BOJ/7579] 앱  (0) 2020.12.29

1. 문제

1) 문제 링크

    : https://programmers.co.kr/learn/courses/30/lessons/1831

 

코딩테스트 연습 - 4단 고음

4단 고음 I'm in my dream~↗ ~↗ ~↗ IU는 본인의 장기인 3단 고음으로 유명하다. 그러던 그녀가 어느 날 4단 고음을 성공했고 그녀의 고음은 학계에서 연구가 될 만큼 유명해졌다 [1]. [1] 견두헌, 배명

programmers.co.kr

 

2) 설명

문제를 읽어보니 *++을 이용해서 n을 만들어내는 경우의 수를 구하는 문제다.(*의 의미는 곱하기 3, +는 더하기 1이다)

다만 여기에 규칙이 몇 개가 있다.

 

규칙 1 : *++은 반드시 *가 나온 후에 + 두 개가 나와야 한다.

규칙 2 : *++ 계산을 하는 중간에 새로운 *++이 시작할 수도 있다.

 

예를 들어

1. *++*++은 올바르다. 이대로 계산을 하면

* + + * + +
3 4 5 15 16 17

이렇게 17을 만들 수 있다.

 

2. 반면 *+++*+ 은 올바르지 않다.

규칙 1에 위배된다.

 

3. *+*+++은 올바르다.

규칙 3에 따라 *+ ( *++) + 이렇게 중간에 새로운 *++이 시작한 것이다.

* + * + + +
3 4 12 13 14 15

이렇게 15를 만들 수 있다.

 

 

2. 풀이

1) 알고리즘

    : 재귀

 

2) 풀이 설명

처음에는 재귀 반복으로 해결하려 했는데 시간초과가 났다ㅠㅠ 그래서 다른 풀이들을 약간 참고했다.

 

이 풀이는 반드시 * 뒤에 ++가 나와야 한다는 규칙을 이용한다.

모든 *가 +에 우선해야 한다는(ex : ******++++++...) 의미가 아니라 *++ 쌍을 이루는 3단 고음이 * + + 순서대로 나와야 한다는 것이다.

dfs(재귀함수)에는 만들고 싶은 수 n과 남아있는 +의 수 plusCnt를 인자로 받는다.

예를 들어 20을 만들고 싶고 +가 4개가 나온 상황이라면 dfs(20, 4)가 된다.

재귀적으로 dfs함수를 호출해 n = 3, plusCnt = 2까지 줄일 수 있는 경우의 수를 세는 게 문제를 푸는 방법이다.

n이 3, plusCnt가 2라는 말은 *++를 의미한다.

n = 3, plusCnt = 2까지 못 만들면 해당 {n, plusCnt} 상태에서는 규칙에 맞게 *++을 만들 수 없다.

 

또한 (앞으로 나올 수 있는 *의 최대 개수 < 반드시 나와야 할 *의 최소 개수) 라면 더이상 재귀함수를 진행하는 것이 무의미하다. 왜냐면 어떻게 진행을 해도 최소한 나와야 하는 *의 개수를 만들 수 없게 되기 때문이다. -> log(n)/log(3) < (plusCnt + 1)/2 이면 진행을 멈춘다. ( log(n)/log(3)은 (로그 3의 n), 즉 3을 몇 번 제곱해야 n이 되냐는 말이다) (plusCnt + 1)인 이유는 plusCnt가 홀수 개일 때를 대비하기 위함이다. (ex : plusCnt = 5일 때 *는 최소 3개가 나와야 한다. 그러려면 (5 + 1)/2로 해줘야 함)

 

n이 3으로 나누어 떨어진다면 + plusCnt가 2 이상이라면 dfs(n/3, plusCnt - 2)를 호출한다.

n을 3으로 나눈다는 것은 *가 하나 등장했다는 의미이고 plusCnt가 2개 이상 있어야 규칙 1을 위반하지 않을 수 있다.

 

n이 3으로 나누어 떨어지든 안 떨어지든 dfs(n - 1, plusCnt + 1)을 호출한다.

n에서 1을 뺀다는 것은 +를 하나 늘렸다는 말이므로 plusCnt + 1을 해주는 것이다.

 

이 과정을 n은 3, plusCnt는 2가 될 때까지 반복해주면 된다.

 

참고로 첫 호출은 dfs(n - 2, 2)로 해주면 된다. 왜냐하면 마지막 두 개는 반드시 ++이기(plusCnt = 2) 때문이다.(그냥 dfs(n, 0)해도 상관 없다)

 

예를 들어서 n = 13일 때 어떻게 진행되는지 보자.

dfs(11, 2) 호출

-> 11%3 != 0이므로 dfs(10, 3)만 호출

    -> 10%3 != 0이므로 dfs(9, 4)만 호출

        -> 9%3 == 0이고 4(plusCnt) >= 2이므로 dfs(3, 2) 호출

            -> n == 3, plusCnt == 2 이니까 return 1

        -> dfs(8, 5) 호출

            -> log(8)/log(3)은 2보다 작고 (5(plusCnt) + 1)/2)인 3보다 작으므로 return 0

        : 1 + 0 해서 return 1

    : return 1

: reuturn 1

 

이런 식으로 진행된다.

 

3. 코드

더보기
#include <math.h>

// 전역 변수를 정의할 경우 함수 내에 초기화 코드를 꼭 작성해주세요.
using namespace std;

int dfs(int n, int plusCnt){
    if(n < 1 || log(n)/log(3) < (plusCnt + 1)/2){
        // log(n)/log(3)	: 앞으로 나올 수 있는 최대한의 * 횟수(n이 되기 위해 3을 거듭제곱해야 하는 횟수)
        // (plusCnt + 1)/2	: *가 나와야 하는 최소한의 횟수
  		// 앞으로 *가 최대로 나오는 경우(log(n)/log(3)) 가 반드시 나와야 하는 *의 횟수((plusCnt + 1)/2) 보다 작다면 더 재귀를 진행해봤자 무의미함으로 return
        return 0;
    }
    if(n == 3 && plusCnt == 2){
        // * : 하나, + : 둘 이므로 1 return
        return 1;
    }
    
    int ret = 0;
    if(n%3 == 0 && plusCnt >= 2){
    	//3으로 나눠지고 남은 +가 두 개 이상이어야 *가 나올 수 있음
        ret += dfs(n/3, plusCnt - 2);
	}
    ret += dfs(n - 1, plusCnt + 1); //+ 하나를 늘리는 경우
    
    return ret;
}

int solution(int n) {
    return dfs(n - 2, 2); // 시작 시 맨 마지막 두 개는 무조건 ++이어야 하므로 plusCnt를 2로 시작
}

 

 

4. 느낀점

오랜만에 log를 봤다...

종종 사용하면 좋을 것 같다.

'알고리즘 > 프로그래머스' 카테고리의 다른 글

[LV2] 뉴스 클러스터링  (0) 2021.08.26
[LV3] 불량 사용자  (0) 2021.08.13
[LV4] 호텔 방 배정  (0) 2021.08.13
[LV2] 거리두기 확인하기  (0) 2021.07.29

ibk 기업은행 면접에서 뼈아픈 패배를 맞은 삐리리뽀옹은 다음 취업 도전에 앞서 ibk 시스템에 원서를 넣었다..

 

돈보다는 개발 커리어에 더 욕심이 있었기에 기업은행보다는 기업은행의 개발을 담당하는 ibk 시스템이 오히려 더 맞지 않을까 했다..

 

하지만 게으름으로 자소서 작성을 미루던 삐리리뽀옹은 여차저차 밤 새워 자소서를 내고... 운좋게 필기시험도 통과하고... 코딩테스트를 보게 되었다..

 

한참 알고리즘에 흥미를 붙이기 시작하고 있던 터라 전날 밤에 약간 설렘 반, 두려움 반의 마음으로 잠자리에 들었다..

 

속세에 떠드는 말 중에 따듯한 햇살과 새 지저귐에 깬다면 지각이다라는 말이 있다...

내가 지각이었다.. 8시 45분까지였는데 8시에 일어난 것이다..(면접장까지 네이버지도 상 50분 정도 걸렸었다..)

 

부리나케 일어나서 초스피드로 대충 씻고 옷을 입었다..

코테 보는 데 쓸데없이 왜 비캐 복장으로 오라고 했는지 투덜대면서 준비를 마치고 뛰쳐나갔다..

초조한 택시 속에서 죄송하지만 기사님을 보채고 보채서 8시 55분 정도에 들어가게 되었다..

 

엥?

 

대기실에 6명밖에 없었다..

뭐가 이상했다...

대기실이 있는 것도 신기한데 6명밖에 없다니..

 

알고보니... 코테가 아니고 1차 기술 면접이었다... ㅎㅎ

 

이건 뭐 멍청한 스스로에게 화낼 새도 없이 도축장으로 끌려갔다...

 

면접은 3대6... 다행이도 내가 맨 마지막 처형이었다..

 

앞사람들 질문 답변할 동안 시뮬레이션 돌리면서 살려고 발버둥쳐댔다...

 

기억에 남는 질문은 "가장 자신 있는 언어가 뭐에요?" 였는데 "c++입니다 ㅎㅎ"라고 대답했던 거였다..

 

물론 지금 생각하면 별 상관 없는 답변이었는데 당시 면접이 끝나고서는 거의 자바만 쓰는 si 회사 면접에서 c도 아닌 c++이 주언어라고 했다고..? 하면서 자책했었다..ㅠㅠ

(이거 말고도 otp 업체에서 인턴 했었습니다 해놓고 otp가 뭐냐니까 모르겠다고 했다거나... ㅎㅎ)

 

여튼 그렇게 내 두 번째 면접은 끝났다...

 

두 번의 기업은행과의 연을 끊어버리고 자책하며 지내던 중에 어이없게도 1차 면접에 합격했다는 통보를 받았다.

 

헐 대박 싶었지만 우연히 얻은 이 기회를 절대 날리지 말자고 다짐했다..

 

지난 면접들의 실수를 반복하지 않기 위해 전날 밤까지 최종 면접 안내사항을 확인하고... 면접 준비도 나름 열심히 하고... 마지막으로 면접을 위해 빌린 양복을 벽에 잘 걸어둔 채 설렘 반 두려움 반으로 잠들었다...

 

 

그리고...

 

 

또 짹짹 소리를 들었다 ㅎㅎ

 

이번에는 8시 25분에 눈을 떴다...(면접 시간은 또 9시였다)

 

이건 뭐 세상 어이가 없어도 ㅋㅋㅋㅋ

 

빤스바람으로 바로 뛰쳐나가도 도저히 도착할 수 없는 시간이었기에...

눈물을 머금고 면접 담당자분께 일이 생겨서 참석하지 못할 것 같다고 전화드리고...

 

울지는 않았지만 한참을 자괴감을 만끽하며 지냈다...

 

이렇게 내 3번의 면접은...

1. 츄리닝...

2. 지각 및 면접보는 줄도 모름...

3. 늦잠으로 불참...

 

이렇게 마무리 지었다....ㅎㅎㅎ

 

다시 생각해도 한심하기 그지없었다..

 

만학도 나이에 저렇게 면접을 다 망쳐대니 죽고 싶었지만 어찌어찌 하다보니 방법은 찾게 되더라...

혹시 이글을 보는 분들 중에 취업하다 자괴감을 느끼는 분이 계시다면... 나같은 놈도 결국 취업은 한다는 사실을 보고 힘내주시기 바랍니다..ㅠㅠ

 

여튼 이 면접에서 내가 느낀 건...!

 

1. 알람은 무조건 크게!

2. 휴대폰 무음은 절대 하면 안 된다!

3. 면접 안내사항 확실하게 확인하기!

 

 

1. GIT 로그인 authentication 변경

 1) 토큰 방식 변경

      오랜만에 git push를 하려는데

remote: Support for password authentication was removed on August 13, 2021. Please use a personal access token instead.

에러가 발생했다...

찾아보니 2021년 8월 13일부터 깃허브에서 authentication 방식을 id/pw 로그인 방식에서 토큰 방식으로 바꿨다고 했다...

그래서 토큰 방식으로 바꾸려고 윈도우 자격 증명 관리자고 뭐고 다 해봤는데 안 되더라..

아마도 내가 소스트리같은 툴은 안 쓰고 커맨드 라인에서 주로 깃을 사용해서 그런 거 같다..

그래서 그냥 싹 밀고 새로 clone을 떴더니 되었다..

 

  2) 무식한 내가 한 방법..

 1. 토큰 생성

github - settings - Developer Settings - personal access token - generate new token - 모두 체크해서 토큰 생성해버리기

이때 토큰은 따로 저장해둬야 한다... 난 메일로 보내두고 스티키 노트에 따로 써두었다..

 

 2. git clone 뜨기

원래는

git clone https://github.com/[깃헙id]/[repository]

방식이었다..

이제 토큰을 적용해서

git clone https://[토큰]@github.com/[깃헙id]/[repository]

이렇게 클론을 떴다..

 

 3. git push 해보기

이전과 똑같이 git push를 해보니 잘 되는 것을 확인했다!

토큰 자체가 인증 수단이기 때문인지 push할 때 id/pw 인증도 따로 안 해도 되는 것 같다.

 

  3) 정석

 1. 2-1)에서 해준 대로 토큰을 생성해준다

 2. git push 후 비밀번호에 토큰 입력!

 3. 매번 토큰 복붙해야 하는 단점이 있지만 이러면 새로 repo 안 따도 된다..

 - 아마 id/token 정보를 따로 파일에 저장해두고 input redirection 하면 매번 입력 안 해도 될 수 있을 것 같다. 이건 나중에 해보고 되면 포스팅하겠다.

  - https://yjm6560.tistory.com/17 에서처럼 git credential 반영구적으로 설정해놓고 해도 될듯..?

'git' 카테고리의 다른 글

[GIT] "Could not resolve host: github.com ERROR  (0) 2020.12.21
[GIT] CLI 사용 시 로그인 계정 저장해두기  (0) 2020.12.17
[GIT] GIT 처음 시작하기  (0) 2020.12.13

1. 문제

1) 문제 링크

    : https://programmers.co.kr/learn/courses/30/lessons/64064

 

코딩테스트 연습 - 불량 사용자

개발팀 내에서 이벤트 개발을 담당하고 있는 "무지"는 최근 진행된 카카오이모티콘 이벤트에 비정상적인 방법으로 당첨을 시도한 응모자들을 발견하였습니다. 이런 응모자들을 따로 모아 불량

programmers.co.kr

 

2) 설명

    user id 배열과 불량 사용자 id 배열이 주어진다.

불량 사용자 id는 중간중간 *(wild mask)가 들어갈 수 있다.(길이는 반드시 맞아야 한다)

이때 불량 사용자가 될 수 있는 user id들의 집합의 수를 구하면 된다.

 

2. 풀이

1) 알고리즘

    : set, bit연산

 

2) 풀이 설명

일단 user_id 배열이나 banned_id 배열이나 8개 이하라서 그냥 조합을 다 살펴보면서 세면 될 것 같았다.

이 조합의 수 세기를 재귀적으로 풀기 위해 전역변수로 my_user_id, my_banned_id, my_answer, id_set 들을 선언해주었다.

(매번 함수 인자로 넘기기 귀찮아서..)

그리고 user_id가 banned_id 에 해당하는지를 확인하는 is_matched함수도 하나 만들었다.(wild mask 때문에 새로 만들어줘야 했다.)

 

calculate_comb_num 함수는 banned_id의 index를 인자로 받아서 거기에 맞는 user_id들을 선택한 다음에  다음에 체크할 banned_id의 순번(banned_idx)와 해당 user_id를 선택했다는 사실(id_bit_check에 선택한 user_id의 index를 &연산)을 인자로 해서 자기 자신을 호출한다.

이 과정은 banned id를 다 셀 때까지 계속되고, 그때 구한 조합의 집합(id_bit_check)가 id_set에 없을 때 myAnswer 값을 1 증가시켜준다.(id_set에 있다는 말은 이전에 이미 카운팅했다는 말임으로)

 

3. 코드

더보기
// 풀이

#include <string>
#include <vector>
#include <set>

using namespace std;

vector<string> my_user_id, my_banned_id;
int my_answer = 0;
set<int> id_set;

bool is_matched(string id, string form){
    //불량사용자 form에 id가 맞는지 확인하는 함수
    if(id.length() != form.length())
        return false;
    for(int i=0;i<id.length();i++){
        if(id[i] == '*' || form[i] == '*')
            continue;
        if(id[i] != form[i])
            return false;
    }
    return true;
}

void calculate_comb_num(int banned_id_idx, int id_bit_check){
    /*
        @banned_id_idx        = 다음에 택할 불량사용자 index
        @id_bit_check = bit로 표현한 불량사용자로 선택된 user 집합
    */
    if(banned_id_idx == my_banned_id.size()){ 
        //불량사용자를 다 배정했으면 진입
        if(id_set.find(id_bit_check) != id_set.end()){
            //이미 셌던 집합이면 return
            return;
        } 
        my_answer++; //조합 개수 증가
        id_set.insert(id_bit_check); //id_set에 추가
        return;
    }
    for(int i=0;i<my_user_id.size();i++){
        if(!(id_bit_check & (1 << i)) && is_matched(my_user_id[i], my_banned_id[banned_id_idx])){
            //불량 사용자로 선택했던 user가 아니면 + 불량사용자 조건에 해당하면...
            calculate_comb_num(banned_id_idx + 1, id_bit_check | (1 << i));
        }
    }
}

int solution(vector<string> user_id, vector<string> banned_id) {
    my_user_id = user_id;
    my_banned_id = banned_id;
    
    calculate_comb_num(0, 0);
    
    return my_answer;
}

 

 

4. 느낀점

문제에서 스네이크 표기법 써서 그냥 통일해서 쓰는데 언더바 치기 귀찮다...

카멜표기법이 게으른 나에게 더 맞는 것 같다..

'알고리즘 > 프로그래머스' 카테고리의 다른 글

[LV2] 뉴스 클러스터링  (0) 2021.08.26
[LV4] 4단 고음  (0) 2021.08.19
[LV4] 호텔 방 배정  (0) 2021.08.13
[LV2] 거리두기 확인하기  (0) 2021.07.29

1. 문제

1) 문제 링크

    : https://programmers.co.kr/learn/courses/30/lessons/64063

 

코딩테스트 연습 - 호텔 방 배정

 

programmers.co.kr

 

2) 설명

   사용자들이 원하는 방의 배열이 주어지고 순서대로 사용자들이 원하는 방에 배정한다.

이미 방이 사용 중이라면 원하는 방보다 높은 숫자의 빈 방 중 가장 낮은 빈 방을 배정받는다.

배정받은 방의 번호들의 배열을 리턴한다.

2. 풀이

1) 알고리즘

    : Solution 1 : set 사용

      Solution 2 : map, find 사용

 

2) 풀이 설명

  풀이 1. set 사용

일단 데이터 타입들이 long long이어서 효율성 문제가 있겠거니 생각했다.

효율성은 나중에 생각하고 일단 풀어보자 생각해서 set을 사용해서 풀어봤다.

 

1. get_allocated_room 함수를 통해 방을 배정받는다.

2. 원하는 방이 이미 배정된 방이라면 다음 방(target_room + 1)을 재귀적으로 확인한다.

3. 배정받는 방은 모두 room_set에 넣어준다.

 

알고리즘은 맞지만 예상대로 효율성을 통과하지 못했다.

왜냐하면 탐색할 때마다 room_set을 탐색해야 하는데 이 탐색이 원하는 방 + 1씩 증가하면서 계속 탐색하기 때문이다.

만약 1~1000,000,000 번 방이 다 꽉 차있고 다음 사람이 1번방을 원한다면 1~1000,000,000번 방을 set에서 찾아봐야 하기 때문이다...

그래서 다른 방법을 생각해봤는데 딱히 안 떠올라서 다른 풀이들을 약간 참고했다.

 

   풀이 2. map, find 사용

set을 사용할 때와 엄청나게 다른 풀이는 아니다. 다만 union-find 알고리즘 일부가 추가되었을 뿐이다.

room_map에는 key는 방 번호이고 value는 (비어있다면 0, 이미 배정된 방이라면 다음에 배정될 수 있는 방 번호) 이 된다.

그래서 탐색할 때마다 이 맵을 업데이트 해준다.

예를 들어 room_map 상황이 다음과 같다고 가정해본다.

{

    1 : 4,

    2 : 4,

    3 : 4,

}

이 상황에서 다음 사람이 2번 방을 원한다면 get_allocated_room 함수에서는

room_map[2]은 0이 아니므로 배정된 방이구나. -> 4번 방으로 가라네? 하고 get_allocated_room(4)를 호출한다.

그리고  room_map[4]는 0이니까 배정된 거네. 하고 4번 방을 리턴한다.

그러면 4번 방은 배정이 된 거니까 그 다음 방인 5번 방이 room_map[4]에 배정된다.

그래서 room_map은 다음과 같이 바뀐다.

{

    1 : 4

    2 : 4

    3 : 4

    4 : 5

}

그리고 다음에 2번방을 원하는 사람이 왔을 때 2 -> 4 -> 5의 탐색 과정을 다시 거치지 않도록 room_map[2]도 5로 업데이트 해준다. (어차피 get_allocated_room 함수는 재귀적으로 돌아가고 배정하는 건 그냥 한 줄 추가하는 거라 시간복잡도에 큰 영향은 없다. 즉 탐색 과정을 줄여서 얻는 속도 절감분이 room_map 업데이트를 위해 한 줄 추가해서 얻는 증가분보다 훨씬 크다)

그러면 room_map은 다시 다음과 같이 바뀐다.

{

    1 : 4,

    2 : 5,

    3 : 4,

    4 : 5

}

물론 1번이나 3번 방을 원하는 사람이 오면 1 -> 4 -> 5 나 3 -> 4 -> 5의 과정을 더 거쳐야하지만 2번 방에서만큼은 확실히 다음 탐색 때 속도를 줄여줄 수 있다.

또한 room_map이 다음 상황일 때는 

{

    1 : 2,

    2 : 3,

    3 : 4,

    4 : 5,

    ... (5 ~ 999999)

    1000000 : 0

}

3번 방을 원하는 사람이 왔을 때 3 -> 4 -> ... -> 1000000까지 탐색해야하지만 중간에 4번 방을 찾는 사람이 있어서 room_map[4] 값을 업데이트 해줬었다면, 즉 room_map이 다음과 같은 상황이라면

{

    1 : 2,

    2 : 3,

    3 : 4,

    4 : 1000000,

    ... (5 ~ 999999)

    1000000 : 0

}

3 -> 4 -> 1000000 으로 훨씬 단축시킬 수 있다.

 

이렇게 함수를 재귀적으로 돌려서 효율성 테스트까지 통과시킬 수 있었다.

3. 코드

더보기
//SOLUTION 1 : SET 사용
#include <vector>
#include <set>

using namespace std;

set<int> room_set;

long long get_allocated_room(long long target_room){
    if(room_set.find(target_room) == room_set.end()){
        return target_room;
    } else {
        return get_allocated_room(target_room + 1);
    }
}

vector<long long> solution(long long k, vector<long long> room_number) {
    vector<long long> answer;
    for(int i=0;i<room_number.size();i++){
        long long allocated_room = get_allocated_room(room_number[i]);
        answer.push_back(allocated_room);
        room_set.insert(allocated_room);
    }
    return answer;
}
//SOLUTION 2 : MAP, FIND 사용

#include <vector>
#include <map>

using namespace std;

map<long long, long long> room_map;

long long get_allocated_room(long long target_room){
    if(room_map[target_room] == 0) {
        return target_room;
    } else {
        room_map[target_room] = get_allocated_room(room_map[target_room]);
        return room_map[target_room];
    }
}

vector<long long> solution(long long k, vector<long long> room_number) {
    vector<long long> answer;
    for(int i=0;i<room_number.size();i++){
		long long allocated_room = get_allocated_room(room_number[i]);
 		answer.push_back(allocated_room);
		room_map[allocated_room] = allocated_room + 1;
    }
    return answer;
}

 

4. 느낀점

union-find를 잘 안 다뤄봐서 생각해내지 못했던 것 같다.

관련 문제들을 좀 더 풀어보자

'알고리즘 > 프로그래머스' 카테고리의 다른 글

[LV2] 뉴스 클러스터링  (0) 2021.08.26
[LV4] 4단 고음  (0) 2021.08.19
[LV3] 불량 사용자  (0) 2021.08.13
[LV2] 거리두기 확인하기  (0) 2021.07.29

첫 이야기를 기업은행 면접에서 광탈한 썰로 시작해보려 한다..

 

삐리리뽀옹은 2019년까지(막학기) 중요한 프로젝트 두 개를 동시에 진행하던 터라 제대로 된 취업 준비를 2020년 상반기부터 시작했다.

 

그래도 경험삼아 몇몇 기업에 원서를 넣어보기는 했는데 그중 하나가 ibk 기업은행이었다...(그때의 나는 금융권 개발자를 꿈꿨다)

 

당시 기은 채용 과정은 일반적인 채용과정과 비슷하게 서류 -> 필기 시험 -> 1차 면접 -> 2차 면접 으로 진행되었는데, 경제학전공빨이 있어서 그런지 운좋게 필기 시험까지 뚫게 됐다.

 

근데 이 1차 면접은 1박2일 합숙 면접으로 진행되는데 나름 바쁜 일이 많던 삐리리뽀옹으로서는 여간 부담이 아닐 수 없었다..

 

괜시리 토끼 세 마리를 한꺼번에 잡으려다가(사실 토끼가 아니라 호랭이었다) 다 놓칠 것만 같은 기분이 들어 고민이 많았다..

 

그러나 토끼 헌터 삐리리뽀옹은 결국 면접 응시를 결심했고, isfp답게 단 하나의 대비 없이 면접을 보러 갔다.(혹시나 하는 마음 반, 경험이라도 쌓자는 마음 반이었다..)

 

약 200여명의 지원자들이 충주 연수원의 대강당에 모여들기 시작했다..

모든 지원자가 모이고 준비가 되자 기은의 굉장히 높아 보이시는 분이 단풍이 왜 붉어지는지를 우리의 청춘과 연관지어 좋은 말씀을 해주셨다..

isfp답게 나는 겉으로는 박수를 치며 속으로는 면접관들이 떨어트린 나같이 힘없는 단풍은 결국 짓밟힌다고 생각했다..

 

여차저차 짐을 풀고 첫 과정을 시작했다.

첫 과정은 약 2시간 가량 진행되는 몸풀기 체육대회같은 거였다..

잉...? 뭔 개발자랑 행원 뽑는 면접에서 체육대회를 시키지...?

굉장히 얼탱이 없었지만 isfp는 결코 속마음을 드러내지 않는다.

토끼 헌터는 누구보다 즐겁게 체육대회를 끝마쳤다.

 

체육대회가 끝나자 안내자분이 "숙소에 돌아가 원한다면 옷 갈아입고 다음 면접에 참석해라.. 그냥 체육대회 복장 그대로 와도 상관 없다.." 라고 말해주셨기에... 난 별 생각 없이 쑥색 반팔에 추리닝 바지... 그리고 바람막이 하나 걸치고 면접장에 갔다..

그 말을 믿다니... 안내자분도 겉과 속이 다른 isfp였음이 분명하다.

200명이 넘는 지원자들이 모두 최소 비캐였고 나 혼자 추리닝차림이었다 ㅋㅋㅋ

백로 노는 데 까마귀 한 마리가 껴버렸다...

(사실 실제로는 크게 상관이 없을 수도 있는데... 당시 첫 면접이었던 터라 이미 내 토끼는 저 멀리 도망가버렸다고 생각했다..)

 

이후 협상 면접, 기술 면접, 마인드맵?같은 면접 등등 여러가지 면접이 있었는데 한 가지도 빠짐없이 다 고꾸라졌다...

얼마나 고꾸라졌냐면 첫날 저녁에 연수원 내 당구장 이용 가능하냐고 질문했다가 빠꾸도 먹었다... 그리고 도저히 회생 가능성이 없다고 생각해서 나중에는 옆 지원자랑 다음 면접을 대기하면서 노트에 오목을 둘 정도였다...(그분도 나와 같은 상황이었다)

 

뭐 여튼 삐리리뽀옹의 첫 면접은 그렇게 어떤 단풍보다 빠르게 땅에 떨어졌다.

 

그러나 이 면접으로부터 정말 많은 것을 느낄 수 있었다..

 

1. 내가 떨어진 것은 절대 옷차림 때문이 아니었다.

 사실 면접 내용은 보안상 자세히 기술할 수 없지만 지금 생각해보면 팀 토의 면접은 여전히 어렵지만 다른 면접들은(특히 기술면접같은 경우에는) 그렇게 어려운 면접은 아니었던 것 같다. 근데 이걸 버벅대고 제대로 대답을 못하니까 당연히 떨어질 수밖에 없었다.. 물론 내 길 잃은 대답에 배드민턴 치다 온 옷차림까지 더해져 면접관님이 더 빠른 탈락 결정을 내릴 수 있게 했을 수도 있다...ㅎㅎ 

 

2. 부정적인 생각보다는 끝까지 최선을 다하자

 사실 체육대회 이후 두세개의 면접 과정에서 난 내 탈락을 결정지었다. 지금 생각해도 너무 확실하긴 했는데 굳이 내가 그걸 결정하고 포기했어야 하나? 생각이 든다. 내가 망했다고 생각하더라도 마지막까지 최선을 다했다면 더 좋은 피드백을 반영할 수 있었을 것 같다는 생각이 든다.

 

3. 관심 없는 혹은 안 될 거라 생각하는 면접도 봐야 한다.

 나중에 면접을 연습하면서 느낀 것이지만 면접 연습과 실제 면접은 차원이 다르다.. 만약 내가 기은 면접에 참석하지 않았다면 다른 중요한 면접에서 이 실수들을 반복했을 수도 있다.. 또한 면접마다 질문, 환경, 분위기 등이 너무 다양하기 때문에 실제로 면접을 볼 수 있는 기회가 있으면 무조건 봐야 한다..

 

 

이런 것들을 배우며... 다음 면접에서는 더 잘하자고... 마음 먹었다...

 

+ Recent posts