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

 

코딩테스트 연습 - 매칭 점수

매칭 점수 프렌즈 대학교 조교였던 제이지는 허드렛일만 시키는 네오 학과장님의 마수에서 벗어나, 카카오에 입사하게 되었다. 평소에 관심있어하던 검색에 마침 결원이 발생하여, 검색개발팀

programmers.co.kr

 

문제 풀이

이 문제는 카카오답게 문자열을 다루는 스킬을 확인하는 문제인 것 같다. 이 문제에서 찾아야할 문자열은 url, 검색하고자 하는 단어, 링크가 걸린 url을 찾아야한다. 각각 다음과 같은 방식으로 찾는다.

 

1. url 검색 : url이 담긴 문자열은 다음과 같이 시작한다.

<meta property="og:url" content="https://

이 문자열로 시작하는 부분을 찾아 content="________"에 밑줄에 해당하는 부분을 저장한다.

 

2. 검색하고자 하는 단어 : 대문자로 한 번, 소문자로 한 번 찾는다.

대문자로 문자를 바꿔주는 toupper()와 소문자로 문자를 바꿔주는 tolower를 이용해 word의 첫 문자를 대문자, 소문자로 찾아 일치하는 단어가 있는지 비교한다. 비교는 모든 문자열을 대문자로 바꿔 비교한다.

 

3. 링크가 걸린 url : 링크 주소 문자열은 다음과 같이 시작한다.

<a href=\"https://

이 문자열로 시작하는 부분을 전부 찾아 자신을 가리키는 페이지의 인덱스를 저장하는 배열에 넣어주고 현재 페이지에서 가리키는 링크의 수를 증가시킨다.

 

위의 과정대로 짠 코드이다.

 

소스 코드

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

string url[21]; //index - url 매핑
double basic[21]; //기본점수
int linknum[21]; //각 노드의 외부링크
map<string, set<int>> links; //자기를 가리키는 링크 저장

bool isSame(string &a, string &b) { //문자열 비교 함수
    for(int i = 0; i < a.length(); i++) {
        if(!isalpha(b[i])) { // 알파벳이 아니면 false 리턴
            return false;
            break;
        }
        if(toupper(a[i]) != toupper(b[i])) { //같지 않으면 false 리턴
            return false;
            break;
        }
    }
    return true;
} 

int solution(string word, vector<string> pages) {
    int answer = 0;
    double mnum = 0;
    for(int i = 0; i < pages.size(); i++) {
        string &now = pages[i];
        //url찾기
        int start = now.find("<meta property=\"og:url\" content=\"https://"); //url이 담긴 문자열 검색
        start += 33; //url 시작 주소로 이동
        int end = now.find("\"", start); //"로 끝나는 위치 저장
        string nowurl = now.substr(start, end - start); //url 저장
        url[i] = nowurl;
        
        
        start = now.find("<html");
        string body = now.substr(start);
        int idx = 0;
        while(idx < body.size()) { //기본 점수 계산 소문자로 찾기
            int pos = body.find(tolower(word[0]), idx);
            if(pos == string::npos) break; //단어 없으면 종료
            if(pos + word.size() > body.size()) break; //범위 벗어나면 종료
            string tmp = body.substr(pos, word.size()); //길이를 벗어나면 break
            if(word.size() == tmp.size() && isSame(word, tmp)) {//길이가 같은 문자열 저장
                if(!isalpha(body[pos-1]) && !isalpha(body[pos + word.size()])) //앞 뒤로 알파벳이 없을 때 증가
                    basic[i]+=1;
            }
            idx = pos+1;
        }
        idx = 0;
        while(idx < body.size()) { //기본 점수 계산 대문자로 찾기
            int pos = body.find(toupper(word[0]), idx); //첫 글자가 같은 위치 검색
            if(pos == string::npos) break; //단어 없으면 종료
            if(pos + word.size() > body.size()) break; //길이를 벗어나면 break
            string tmp = body.substr(pos, word.size()); //길이가 같은 문자열 저장
            if(word.size() == tmp.size() && isSame(word, tmp)) { //사이즈도 같고 같은 문자일 때
                if(!isalpha(body[pos-1]) && !isalpha(body[pos + word.size()])) //앞 뒤로 알파벳이 없을 때 증가
                    basic[i]+=1;
            }
            idx = pos+1; //다음 위치부터 다시 검색
        }
        idx = 0;
        while(idx < body.size()) { //링크 주소 찾기
            int start = body.find("<a href=\"https://", idx);
            if(start == string::npos) break;
            start += 9;
            int end = body.find("\"", start);
            string link = body.substr(start, end - start); //링크 문자열
            links[link].insert(i); //자신을 가리키는 페이지 인덱스에 insert
            linknum[i]++; //현재 페이지의 링크 수 증가
            idx = end;
        }
    }

    for(int i = 0; i < pages.size(); i++) { //최고점 찾기
        double score = basic[i]; //기본 점수
        for(auto it = links[url[i]].begin(); it != links[url[i]].end(); it++) {
            score += ((basic[*it]) / linknum[*it]); //링크 점수 더하기
        }
        if(mnum < score) { //최대값 갱신
            mnum = score;
            answer = i;
        }
    }
    


    return answer;
}

index - url 매핑한 url 배열, 기본점수를 저장할 basic 배열, 각 노드의 외부 링크 수를 저장할 linknum배열, 자신을 가리키는 링크의 인덱스를 저장할 links map을 선언하였다.

 

주의할 점과 실수한 부분

이 문제는 문자열을 파싱하는 부분이 조금 까다로웠고 점수가 double형으로 계산되어야 했다. 처음 int형으로 basic을 저장하고 캐스팅하여 점수를 계산하였을 때 몇 개의 테스트케이스를 통과하지 못했었다. 그 다음 최대값을 저장한 mnum 변수도 int형으로 선언되어있었는데 이것 때문에 8번, 17번만 통과를 하지 못하고 있었다. 이 실수를 모르고 계속 다른 부분에서 오류를 찾아 시간낭비를 많이 했다. mnum도 double로 바꾸는 순간 모든 테스트 케이스도 통과할 수 있었다.

 

채점 결과

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

 

코딩테스트 연습 - 순위 검색

["java backend junior pizza 150","python frontend senior chicken 210","python frontend senior chicken 150","cpp backend senior pizza 260","java backend junior chicken 80","python backend senior chicken 50"] ["java and backend and junior and pizza 100","pyt

programmers.co.kr

이 문제는 스스로 시간초과 문제를 해결하지 못하여 풀이를 보고 해결할 수 있었다.

문제

카카오는 하반기 경력 개발자 공개채용을 진행 중에 있으며 현재 지원서 접수와 코딩테스트가 종료되었습니다. 이번 채용에서 지원자는 지원서 작성 시 아래와 같이 4가지 항목을 반드시 선택하도록 하였습니다.

  • 코딩테스트 참여 개발언어 항목에 cpp, java, python 중 하나를 선택해야 합니다.
  • 지원 직군 항목에 backend와 frontend 중 하나를 선택해야 합니다.
  • 지원 경력구분 항목에 junior와 senior 중 하나를 선택해야 합니다.
  • 선호하는 소울푸드로 chicken과 pizza 중 하나를 선택해야 합니다.

인재영입팀에 근무하고 있는 니니즈는 코딩테스트 결과를 분석하여 채용에 참여한 개발팀들에 제공하기 위해 지원자들의 지원 조건을 선택하면 해당 조건에 맞는 지원자가 몇 명인 지 쉽게 알 수 있는 도구를 만들고 있습니다.
예를 들어, 개발팀에서 궁금해하는 문의사항은 다음과 같은 형태가 될 수 있습니다.
코딩테스트에 java로 참여했으며, backend 직군을 선택했고, junior 경력이면서, 소울푸드로 pizza를 선택한 사람 중 코딩테스트 점수를 50점 이상 받은 지원자는 몇 명인가?

물론 이 외에도 각 개발팀의 상황에 따라 아래와 같이 다양한 형태의 문의가 있을 수 있습니다.

  • 코딩테스트에 python으로 참여했으며, frontend 직군을 선택했고, senior 경력이면서, 소울푸드로 chicken을 선택한 사람 중 코딩테스트 점수를 100점 이상 받은 사람은 모두 몇 명인가?
  • 코딩테스트에 cpp로 참여했으며, senior 경력이면서, 소울푸드로 pizza를 선택한 사람 중 코딩테스트 점수를 100점 이상 받은 사람은 모두 몇 명인가?
  • backend 직군을 선택했고, senior 경력이면서 코딩테스트 점수를 200점 이상 받은 사람은 모두 몇 명인가?
  • 소울푸드로 chicken을 선택한 사람 중 코딩테스트 점수를 250점 이상 받은 사람은 모두 몇 명인가?
  • 코딩테스트 점수를 150점 이상 받은 사람은 모두 몇 명인가?
  • query의 각 문자열은 "[조건] X" 형식입니다.
    • [조건]은 "개발언어 and 직군 and 경력 and 소울푸드" 형식의 문자열입니다.
    • 언어는 cpp, java, python, - 중 하나입니다.
    • 직군은 backend, frontend, - 중 하나입니다.
    • 경력은 junior, senior, - 중 하나입니다.
    • 소울푸드는 chicken, pizza, - 중 하나입니다.
    • '-' 표시는 해당 조건을 고려하지 않겠다는 의미입니다.
    • X는 코딩테스트 점수를 의미하며 조건을 만족하는 사람 중 X점 이상 받은 사람은 모두 몇 명인 지를 의미합니다.
    • 각 단어는 공백문자(스페이스 바) 하나로 구분되어 있습니다.
    • 예를 들면, "cpp and - and senior and pizza 500"은 "cpp로 코딩테스트를 봤으며, 경력은 senior 이면서 소울푸드로 pizza를 선택한 지원자 중 코딩테스트 점수를 500점 이상 받은 사람은 모두 몇 명인가?"를 의미합니다.

조건이 주어지는 query를 만족하는 인원이 몇명인지를 vector에 담아 리턴하는 문제이다.

 

예시

위 그림과 같이 info로 각 사람들의 지원 조건이 주어지면 query를 만족하는 인원이 각각 몇명인지를 구하는 문제이다.

 

이 문제는 각각 인원을 모두 구하고 겹치는 인원들을 찾아내어 인원을 세면 시간초과가 걸린다. 따라서 각 조건을 만족하는 사람을 전부 가지고 유지하고 있어야한다. 따라서 info에 주어지는 언어, 직군, 경력, 음식을 하나로 생각해서 그 조건을 만족하는 사람들의 점수를 가지고 있어야한다. 글쓴이는 언어, 직군, 경력,음식을 각가 1차원으로 생각하여 4차원 배열을 만들었다. 코드는 다음과 같다.

 

소스코드

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

vector<int> database[3][2][2][2]; //데이터베이스 구축
//cpp = 0, java = 1, python = 2;
//backend = 0, frontend = 1;
//junior = 0, senior = 1;
//chicken = 0, pizza = 1;


vector<int> solution(vector<string> info, vector<string> query) {
    vector<int> answer;
    for(int i =0; i < info.size(); i++) {
        int lan, end, career, food;
        int now = 0;
        string s = info[i]; //문자열을 나눔
        if(s[0] == 'c') {
            lan = 0;
            now = 4;
        }
        else if(s[0] == 'j') {
            lan = 1;
            now = 5;
        }
        else{
            lan = 2;
            now = 7;
        }
        
        if(s[now] == 'b') {
            end = 0;
            now += 8;
        }
        else {
            end = 1;
            now += 9;
        }
        
        if(s[now] == 'j') {
            career = 0;
        }
        else {
            career = 1;
        }
        now += 7;
        
        if(s[now] == 'c') {
            food = 0;
            now += 8;
        }
        else {
            food = 1;
            now += 6;
        }
        
        int num = stoi(s.substr(now));
        database[lan][end][career][food].push_back(num); //해당하는 데이터베이스에 점수 추가
    }
    
  for(int i=0; i<3; i++)
    for(int j=0; j<2; j++)
      for(int k=0; k<2; k++)
        for(int l=0; l<2; l++)
          sort(database[i][j][k][l].begin(),database[i][j][k][l].end());
    
    for(string q : query) {
        int now = 0;
        int s1, s2, s3, s4, e1, e2, e3, e4;
        int lan = -1, end = -1, career = -1, food = -1;
        if(q[now] == 'c') { //언어 분류
            lan = 0;
            now += 8;
        }
        else if(q[now] == 'j') {
            lan = 1;
            now += 9;
        }
        else if(q[now] == 'p') {
            lan = 2;
            now += 11;
        }
        else {
            now += 6;
        }
        
        if(q[now] == 'b') {
            end = 0;
            now += 12;
        }
        else if(q[now] == 'f') {
            end = 1;
            now += 13;
        }
        else {
            now += 6;
        }
        
        if(q[now] == 'j') {
            career = 0;
            now += 11;
        }
        else if(q[now] == 's') {
            career = 1;
            now += 11;
        }
        else {
            now += 6;
        }
        
        if(q[now] == 'p') {
            food = 1;
            now += 6;
        }
        else if(q[now] == 'c') {
            food = 0;
            now += 8;
        }
        else {
            now += 2;
        }
        
        int num = stoi(q.substr(now));
        
        int people = 0;
        for(int i = 0; i <= 2; i++) {
            if(lan != i && lan != -1) continue;
            for(int j = 0; j <= 1; j++) {
                if(end != j && end != -1) continue;
                for(int k = 0; k <= 1; k++) {
                    if(career != -1 && career != k) continue;
                    for(int l = 0; l <= 1; l++) {
                        if(food != -1 && food != l) continue;
                        if(database[i][j][k][l].size()) {
                            auto lo = lower_bound(database[i][j][k][l].begin(), database[i][j][k][l].end(), num);
                            people += database[i][j][k][l].end() - lo;
                        }
                    }
                }
            }
        }
        answer.push_back(people);
    }
    
    return answer;
}

문자열을 분류하는 코드 때문에 상당히 복잡해보이지만 그 부분을 제외하면 간단한 코드이다. 각 조건에 따라서 언어, 직군, 경력, 음식에 해당하는 인덱스가 결정된다. 그 조건을 만족하는 벡터에 점수를 push한다. 그렇다면 같은 조건을 가진 사람들의 점수가 유지될 것이다. 그리고 조건이 되는 점수를 이분탐색하기 위하여 모두 정렬한다. 4중 for문이지만 3 * 2 * 2 * 2 = 24로 24번밖에 반복하지 않는다. 그 다음 query에서 주어지는 조건에 따라 탐색하고자 하는 인덱스를 결정한다. 모든 초기값을 -1로 주어 만약 -1인 경우 해당 조건의 모든 점수를 탐색할 수 있게 하였다. 위 코드를 보았을 때 

 if(lan != i && lan != -1) continue;

이 부분은 언어가 i번째 인덱스도 아니고 -1도 아닐 경우 건너 뛴다는 의미이다. 즉 -1인 경우 모든 인덱스를 탐색하고 그렇지 않은 경우 원하는 조건인 lan에 해당하는 인덱스만 탐색한다는 뜻이다. 그렇게 4가지 조건을 모두 탐색하여 lower_bound라는 이분탐색 알고리즘을 이용하여 조건을 만족하는 점수의 이터레이터를 얻는다. 그리고 end와의 차이를 이용해 조건으로 주어진 값보다 크거나 같은 값의 수를 구할 수 있다. 그렇게 구한 인원을 각 query마다 answer에 push_back하여 원하는 값을 구할 수 있다.

 

채점 결과

 

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

 

코딩테스트 연습 - 110 옮기기

0과 1로 이루어진 어떤 문자열 x에 대해서, 당신은 다음과 같은 행동을 통해 x를 최대한 사전 순으로 앞에 오도록 만들고자 합니다. x에 있는 "110"을 뽑아서, 임의의 위치에 다시 삽입합니다. 예를

programmers.co.kr

문제

0과 1로 이루어진 어떤 문자열 x에 대해서, 당신은 다음과 같은 행동을 통해 x를 최대한 사전 순으로 앞에 오도록 만들고자 합니다.

  • x에 있는 "110"을 뽑아서, 임의의 위치에 다시 삽입합니다.

예를 들어, x = "11100" 일 때, 여기서 중앙에 있는 "110"을 뽑으면 x = "10" 이 됩니다. 뽑았던 "110"을 x의 맨 앞에 다시 삽입하면 x = "11010" 이 됩니다.

변형시킬 문자열 x가 여러 개 들어있는 문자열 배열 s가 주어졌을 때, 각 문자열에 대해서 위의 행동으로 변형해서 만들 수 있는 문자열 중 사전 순으로 가장 앞에 오는 문자열을 배열에 담아 return 하도록 solution 함수를 완성해주세요.

 

풀이

    이 문제는 혼자 풀었을 때 시간초과를 해결하지 못하여 풀이를 보고 풀었다. 우선 110이 사라지고 난 다음 생기는 110도 처리를 해주어야된다는 것을 뒤늦게 알았다. 풀이를 보고 stack을 이용하면 쉽게 풀린다는 것을 알았다. 또 110을 삽입하는 위치가 오른쪽에서 시작하여 연속되는 1의 앞부분에 붙여 넣으면 된다는 것을 문제만 보고 떠올리기 쉽지 않았다. 이 부분만 알고 풀면 굉장히 쉽게 풀 수 있는 문제이다. 코드의 순서는 다음과 같다.

 

  1. 지워진 문자열의 문자를 하나씩 stack에 넣으며 110을 찾으면 지우고 카운트(110의 개수)를 센다.
  2. 110이 모두 지워진 문자열에서 뒤에서부터 1이 아닌 숫자가 나올 때까지 탐색한다. 즉 예를들어 001001111이 있을 경우 110들은 00100____1111 위치에 들어가야지 최소 숫자가 나오게 된다.
  3. 위치를 찾은 경우 삽입하려는 위치 앞부분의 문자열을 정답 문자열에 붙여넣는다.
  4. 110의 개수만큼 정답 문자열에 붙여넣는다.
  5. 삽임하려는 위치 뒷부분 문자열을 정답 문자열에 붙여넣는다.

코드

#include <string>
#include <vector>
#include <iostream>

using namespace std;

vector<string> solution(vector<string> s) {
    vector<string> answer;
    vector<char> st; //110을 지우고 남은 문자열
    int cnt = 0; //지워진 110의 개수
    for(string str : s) {
        cnt = 0;
        st.clear();
        for(int i = 0; i < str.size(); i++) { //110을 모두 지우기
            if(str[i] == '1') st.push_back('1'); //만약 0이 아니면 스택에 넣음
            else {
                if(st.size() >= 2 && st[st.size()-1] == '1' && st[st.size()-2] == '1') { //110을 찾으면 110개수를 늘리고 스택에서 지우기
                    cnt++;
                    st.pop_back();
                    st.pop_back();
                }
                else
                    st.push_back('0');
            }
        }
        int i;
        for(i = st.size()-1; i >= 0; i--) { //뒤에서 시작하여 연속된 1의 앞부분에 110붙이기
            if(st[i] == '1') continue;
            else break;
        }
        
        string ret = ""; 
        
        for(int k = 0; k <= i; k++) //연속된 1 앞부분 붙여넣기
            ret += st[k];
        
        for(int c = 0; c < cnt; c++) //110 붙여넣기
            ret += "110";
        
        for(int k = i+1; k < st.size(); k++) //연속된 1 뒷부분 붙여넣기
            ret += st[k];
        
        answer.push_back(ret);
    }
    
    return answer;
}

결과

 

 

풀이 방법만 알면 절대 어렵지 않은 문제였지만 이런 풀이과정을 생각해낸다는 것이 굉장히 어렵다. stack을 잘 다룬다고 생각하였지만 이렇게 실전에 적용하는 생각은 잘 하지 못하였다. 스택, 큐, 힙 등을 실제에 사용하는 문제를 많이 풀어봐야할 것 같다.

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

 

코딩테스트 연습 - 모두 0으로 만들기

각 점에 가중치가 부여된 트리가 주어집니다. 당신은 다음 연산을 통하여, 이 트리의 모든 점들의 가중치를 0으로 만들고자 합니다. 임의의 연결된 두 점을 골라서 한쪽은 1 증가시키고, 다른 한

programmers.co.kr

문제 설명

각 점에 가중치가 부여된 트리가 주어집니다. 당신은 다음 연산을 통하여, 이 트리의 모든 점들의 가중치를 0으로 만들고자 합니다.

  • 임의의 연결된 두 점을 골라서 한쪽은 1 증가시키고, 다른 한쪽은 1 감소시킵니다.

하지만, 모든 트리가 위의 행동을 통하여 모든 점들의 가중치를 0으로 만들 수 있는 것은 아닙니다. 당신은 주어진 트리에 대해서 해당 사항이 가능한지 판별하고, 만약 가능하다면 최소한의 행동을 통하여 모든 점들의 가중치를 0으로 만들고자 합니다.

트리의 각 점의 가중치를 의미하는 1차원 정수 배열 a와 트리의 간선 정보를 의미하는 edges가 매개변수로 주어집니다. 주어진 행동을 통해 트리의 모든 점들의 가중치를 0으로 만드는 것이 불가능하다면 -1을, 가능하다면 최소 몇 번만에 가능한지를 찾아 return 하도록 solution 함수를 완성해주세요. (만약 처음부터 트리의 모든 정점의 가중치가 0이라면, 0을 return 해야 합니다.)

 

문제 예시

문제 예시

위의 그림처럼 모든 하나의 노드는 증가, 하나의 노드는 감소시키면서 모든 노드의 가중치를 0으로 만들 경우 증가나 감소시키는 횟수를 리턴하면 된다.

 

문제 풀이

위 예시에서 보았듯이 이 문제는 리프노드 즉 가장 끝에 있는 노드부터 순서대로 0으로 만들어가다보면 풀리는 문제이다. 따라서 깊이우선 탐색을 하며 자식 노드부터 순서대로 0으로 만들어가면 문제를 풀 수 있다. 그렇다면 자식 노드부터 0으로 만들기 위해선 코드상에서 어떻게 해야할까?

#include <string>
#include <vector>
#include <iostream>
#include <cmath>

using namespace std;

vector<long long> weight; //각 노드의 현재 가중치 저장

vector<int> g[300001]; //인접리스트를 이용한 그래프 표현
bool visit[300001]; //방문여부 표시

long long answer;

void dfs(int now) {
    visit[now] = true; //현재 노드 방문 체크
    for(int child : g[now]) { //자식 노드를 돌며
        if(visit[child]) continue; //방문노드(부모노드)는 스킵
        dfs(child); //자식노드를 먼저 탐색하고
        answer += abs(weight[child]); //증가/감소하는 횟수 더하기
        weight[now] += weight[child]; //부모노드의 가중치 증가/감소
        weight[child] = 0; //자식노드 0으로 만들기
    }
}

long long solution(vector<int> a, vector<vector<int>> edges) {

    for(int e : a) {
        weight.push_back((long long)e); //long long형으로 가중치 배열 다시 만들기
    }
    
    for(vector<int> e : edges) { //인접리스트 그래프 만들기
        g[e[0]].push_back(e[1]);
        g[e[1]].push_back(e[0]);
    }
    
    dfs(0); //dfs탐색
    
    if(weight[0] != 0) return -1; //모두 0으로 만들지 못한 경우 -1리턴
    else return answer; //그렇지 않은 경우 정답 리턴
}

이 코드와 같이 자식노드를 먼저 탐색한 다음 부모노드의 가중치를 수정하면 된다. 위 코드의 19~22번줄과 같이 dfs(child)로 자식노드를 먼저 탐색하고서 부모노드의 가중치를 수정하고 자식노드를 0으로 만드는 것을 알 수 있다. 현재 노드의 자식노드가 있을 때 그 자식노드가 리프노드가 아닐 경우 리프노드부터 차례대로 0으로 만들어 올라오며 수정된 가중치를 가지고 현재 노드를 수정해야하기 때문이다. 이렇게 가중치를 계산하는 부분의 위치만 잘 생각하면 쉽게 풀 수 있는 문제였다.

 

채점 결과

 

채점 결과

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

 

코딩테스트 연습 - 기둥과 보 설치

5 [[1,0,0,1],[1,1,1,1],[2,1,0,1],[2,2,1,1],[5,0,0,1],[5,1,0,1],[4,2,1,1],[3,2,1,1]] [[1,0,0],[1,1,1],[2,1,0],[2,2,1],[3,2,1],[4,2,1],[5,0,0],[5,1,0]] 5 [[0,0,0,1],[2,0,0,1],[4,0,0,1],[0,1,1,1],[1,1,1,1],[2,1,1,1],[3,1,1,1],[2,0,0,0],[1,1,1,0],[2,2,0,1]] [[

programmers.co.kr

문제 설명

이 문제는 접근 방법이 까다로웠던 문제이다. 문제의 조건은 다음과 같다.

  • 기둥은 바닥 위에 있거나 보의 한쪽 끝 부분 위에 있거나, 또는 다른 기둥 위에 있어야 합니다.
  • 보는 한쪽 끝 부분이 기둥 위에 있거나, 또는 양쪽 끝 부분이 다른 보와 동시에 연결되어 있어야 합니다.

문제의 입력으로 벽면의 크기 n과 기둥과 보를 설치하거나 삭제하는 작업이 순서대로 담긴 2차원 배열 build_frame이 주어진다. build_frame의 원소는 다음과 같다.

  • build_frame의 원소는 [x, y, a, b]형태입니다.
    • x, y는 기둥, 보를 설치 또는 삭제할 교차점의 좌표이며, [가로 좌표, 세로 좌표] 형태입니다.
    • a는 설치 또는 삭제할 구조물의 종류를 나타내며, 0은 기둥, 1은 보를 나타냅니다.
    • b는 구조물을 설치할 지, 혹은 삭제할 지를 나타내며 0은 삭제, 1은 설치를 나타냅니다.
    • 벽면을 벗어나게 기둥, 보를 설치하는 경우는 없습니다.
    • 바닥에 보를 설치 하는 경우는 없습니다.
    • 구조물은 교차점 좌표를 기준으로 보는 오른쪽, 기둥은 위쪽 방향으로 설치 또는 삭제합니다.

예시 그림

위 그림에 대한 입력의 예시는 다음과 같다.

입출력 결과

풀이

코드는 다음과 같다.

#include <string>
#include <vector>
#include <algorithm>

using namespace std;

bool graph[4][101][101]; //좌표의 상하좌우 표시

bool ans(vector<int> a, vector<int> b) { //결과 정렬 비교함수
    if(a[0] == b[0]) {
        if(a[1] == b[1]) {
            return a[2] < b[2];
        }
        else {
            return a[1] < b[1];
        }
    }
    else {
        return a[0] < b[0];
    }
}

vector<vector<int>> solution(int n, vector<vector<int>> build_frame) {
    vector<vector<int>> answer;
    for(vector<int> element : build_frame) {
        int x = element[0], y = element[1];
        int construct = element[2], install = element[3];
        
        if(install) { //설치인 경우
            if(construct == 0) { //기둥인 경우
                if(y == 0) { //바닥인 경우 그냥 설치
                    graph[0][x][y] = 1;
                    graph[1][x][y+1] = 1;
                }
                else { //바닥이 아닌 경우
                    if(graph[1][x][y] || graph[2][x][y] || graph[3][x][y]) { //보의 끝이거나 기둥 위에 올릴 수 있음
                        graph[0][x][y] = 1;
                        graph[1][x][y+1] = 1;
                    }
                }
            }
            else { //보인 경우
                if(graph[1][x][y] || graph[1][x+1][y]) { //한쪽 끝이 기둥 윗부분인 경우
                    graph[3][x][y] = 1;
                    graph[2][x+1][y] = 1;
                } 
                else if(graph[2][x][y] && graph[3][x+1][y]) { //양쪽 끝이 보인 경우
                    graph[3][x][y] = 1;
                    graph[2][x+1][y] = 1;
                }
            }
        }
        else { //제거인 경우
            if(construct == 0) { //기둥을 삭제할 경우
                bool chk = 0;
                if(graph[0][x][y+1]) { //위에 기둥이 있는 경우
                    if(!(graph[2][x][y+1] || graph[3][x][y+1]))  chk = 1; //보가 있으면 삭제 가능
                }
                if(graph[2][x][y+1]) { //왼쪽으로 보가 있는 경우
                    if(!(graph[2][x-1][y+1] && graph[3][x][y+1]) && !graph[1][x-1][y+1]) chk = 1; //양쪽에 보가 있으면 삭제 가능
                }
                if(graph[3][x][y+1]) { //오른쪽으로 보가 있는 경우
                    if(!(graph[2][x][y+1] && graph[3][x+1][y+1]) && !graph[1][x+1][y+1]) chk = 1; //양쪽에 보가 있으면 삭제 가능    
                }
                if(!chk) {
                    graph[0][x][y] = 0;
                    graph[1][x][y+1] = 0;
                }
            }
            else { //보를 삭제하는 경우
                bool chk = 0;
                if(graph[0][x][y]) { //위에 기둥이 있는 경우
                    if(!(graph[2][x][y] || graph[1][x][y])) chk = 1; //왼쪽으로 보도 없고 아래에 기둥도 없는 경우 삭제 불가
                }
                if(graph[0][x+1][y]) { //위에 기둥이 있는 경우
                    if(!(graph[3][x+1][y] || graph[1][x+1][y])) chk = 1;
                }
                if(graph[2][x][y]) { //왼쪽에 보가 있는 경우
                    if(!(graph[1][x][y] || graph[1][x-1][y])) chk = 1; //왼쪽 보가 기댈 기둥이 하나도 없는 경우 삭제 불가
                }
                if(graph[3][x+1][y]) { //오른쪽에 보가 있는 경우
                    if(!(graph[1][x+1][y] || graph[1][x+2][y])) chk = 1;
                }
                if(!chk) {
                    graph[3][x][y] = 0;
                    graph[2][x+1][y] = 0;
                }
            }
        }
    }
    for(int i = 0; i <= n; i++) {
        for(int j = 0; j <= n; j++) {
            if(graph[0][i][j]) answer.push_back({i, j, 0}); //기둥 구조물 저장
            if(graph[3][i][j]) answer.push_back({i, j, 1}); //보 구조물 저장
        }
    }
    sort(answer.begin(), answer.end(), ans); //결과 정렬
    return answer;
}

우선 마지막 조건을 보고 좌표에 시작점만을 표시한다면 문제를 풀기 굉장히 어려워진다. 따라서 3차원 배열을 이용하여 각 좌표에 상하좌우에 구조물이 있는지를 표시하였다. 그림으로 나타내면 다음과 같다.

예를 들어 (x,y)에 오른쪽과 아래로 구조물이 존재한다면 x,y 좌표의 오른쪽과 아래쪽에 해당하는 값만 1이다. 즉 다음과 같다.

graph[0][x][y] = 0; //상
graph[1][x][y] = 1; //하
graph[2][x][y] = 0; //좌
graph[3][x][y] = 1; //우

이렇게 한다면 구조물의 시작부분뿐만 아니라 끝부분도 표시할 수 있다. 1과 0의 값만 사용하기 때문에 메모리 절약을 위해 bool 타입으로 선언하였다. 설치하고 제거하는 조건은 문제에 나와있는 경우의 수를 생각해보았다.

 

1. 기둥을 설치하는 경우

  • 만약 바닥인 경우 아무 조건 없이 설치
  • 보의 끝이거나 기둥의 위인 경우 설치

2. 보를 설치하는 경우

  • 한쪽 끝이 기둥 윗부분인 경우 설치
  • 양쪽 끝이 보인 경우 설치

제거하는 경우는 모든 조건이 만족할 경우 제거를 할 수 있다. 따라서 제거하지 못할 조건을 체크하였다.

3. 기둥을 제거하는 경우

  • 위에 기둥이 있을 경우
    • 왼쪽 오른쪽에 보가 하나도 없을 경우 제거 불가
  • 왼쪽에 보가 있을 경우
    • 보의 왼쪽 오른쪽 모두 보가 존재하지 않을 경우 제거 불가
    • 보를 지탱하는 기둥이 하나도 없을 경우 제거 불가
  • 오른쪽에 보가 있을 경우
    • 보의 왼쪽 오른쪽 모두 보가 존재하지 않을 경우 제거 불가
    • 보를 지탱하는 기둥이 하나도 없을 경우 제거 불가

위 조건을 모두 만족할 경우 제거 가능

 

4. 보를 제거하는 경우

  • 바로 위에 기둥이 있는 경우
    • 왼쪽에 보가 없고 아래에 기둥도 없는 경우 제거 불가
  • 제거하려는 보의 다른쪽 끝 위에 기둥이 있는 경우
    • 그 기둥을 지탱하는 보나 기둥이 없는 경우 제거 불가
  • 왼쪽에 보가 있는 경우(한쪽이 연결된 보가 제거되므로 기둥만 생각하면 됨)
    • 보가 지탱할 기둥이 하나도 없는 경우 제거 불가
  • 오른쪽에 보가 있는 경우
    • 보가 지탱할 기둥이 하나도 없는 경우 제거 불가

위의 규칙에 따라 기둥을 하나씩 설치하고 제거한 다음 구조물이 그래프에 완성된다. 구조물의 결과를 리턴해야하므로 각 좌표를 하나씩 순회하며 기둥은 위로, 보는 오른쪽으로 설치하므로 graph의 1차원 인덱스가 0이나 3일 경우 값이 참일 때 정답 벡터에 좌표와 기둥인지 보인지를 push_back해준다. 모두 넣은 다음 answer를 조건에 따라 정렬하여 리턴하면 된다. 위 코드를 실행할 경우 다음과 같이 모두 통과하는 것을 볼 수 있다.

결과 캡쳐

 

+ Recent posts