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

 

코딩테스트 연습 - 외벽 점검

레스토랑을 운영하고 있는 "스카피"는 레스토랑 내부가 너무 낡아 친구들과 함께 직접 리모델링 하기로 했습니다. 레스토랑이 있는 곳은 스노우타운으로 매우 추운 지역이어서 내부 공사를 하

programmers.co.kr

 

풀이

    처음 문제를 O(N)만에 풀 수 있는 방법을 생각해보았지만 뜻대로 되지 않고 자꾸 DFS를 이용하는 방법이 떠올라 DFS를 이용하지 않고 풀어보려했지만 결국 DFS를 사용하게 되었다. 이 문제가 까다로운 부분은 원형으로 연결되어 있다는 것이다. 1차원 배열의 맨 끝에서 오른쪽으로 한 번 더 이동하면 첫 번째 원소에 와야하는 것과 같은 예외처리를 해야한다. 문제는 다음과 같이 풀었다.

  1. DFS를 이용하여 임의의 취약지점에서 출발한다.
  2. 점검할 수 있는 취약지점까지 점검한다.
  3. 다음 취약지점부터 아직 점검하지 않은 친구가 점검할 수 있는 거리만큼 점검한다.
  4. 만약 점검한 취약지점 수와 전체 취약지점 수가 같은 경우 결과값을 최소값으로 갱신한다.

 

소스 코드

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

using namespace std;

int answer = 1e9; //정답 최대값으로 설정
int N;

vector<bool> dvisit; //점검을 완료한 친구 표시

void dfs(int ans, int now, int len, int total, vector<int> &weak, vector<int> &dist) {
    int start = weak[now]; //시작 취약지점
    int limit = weak[now] + dist[len]; //점검할 수 있는 거리의 마지노선
    int cnt = 0; //점검한 취약지점 수
    if(limit >= N) { //만약 범위를 벗어나 다시 0부터 시작하는 경우
        while(true) {
            if((weak[now] >= start) || (weak[now] <= (limit % N))) { //다음 탐색할 취약지점이 점검할 수 있는  범위에 있는 경우
                now++; //다음 취약지점으로 이동
                now %= weak.size(); //범위 벗어나지 않게 처리
                cnt++; //점검한 취약지점 수 증가
                if(cnt + total == weak.size()) { //취약지점 모두 점검한 경우
                    answer = min(answer,ans); //최소값 갱신
                    return; //리턴
                }
            }
            else { //범위를 벗어난 경우 점검 종료
                break;
            }
        }
    }
    else { //만약 범위를 벗어나지 않는 경우
        while(true) {
            if((weak[now] >= start) && (weak[now] <= limit)) { //다음 탐색할 취약지점이 점검할 수 있는 범위에 있는 경우
                now++; //다음 취약지점으로 이동
                now %= weak.size(); //범위 벗어나지 않게 처리
                cnt++; //점검한 취약지점 수 증가
                if(cnt + total == weak.size()) { //취약지점 모두 점검한 경우
                    answer = min(answer,ans); //최소값 갱신
                    return; //리턴
                }
            }
            else { //범위를 벗어난 경우 점검 종료
                break;
            }
        }
    }
    for(int d = 0; d < dist.size(); d++) { //점검할 다음 친구 선택
        if(dvisit[d]) continue; //이미 점검한 친구는 패스
        dvisit[d] = true; //방문표시
        dfs(ans+1 , now, d, total + cnt, weak, dist); //DFS를 이용하여 now부터 취약지점 점검
        dvisit[d] = false; //방문표시 취서
    }
}

int solution(int n, vector<int> weak, vector<int> dist) {
    N = n; //외벽길이 전역변수로 저장
    dvisit.resize(dist.size()); //방문배열 크기 설정
    
    sort(dist.begin(), dist.end(), greater<int>()); //큰 것부터 정렬
    
    for(int w = 0; w < weak.size(); w++) { //점검을 시작할 취약지점 위치
        for(int d = 0; d < dist.size(); d++) { //점검할 친구 선택
            dvisit[d] = true;
            dfs(1, w, d, 0, weak, dist);
            dvisit[d] = false;
        }
    }
    if(answer == 1e9) return -1; //모두 탐색하지 못한 경우 -1 리턴
    else return answer; //그렇지 않은 경우 최소값 리턴
}

최대한 반복되는 비교를 하지 않도록 방문함수를 설정하고 점검한 취약지점을 다시 점검하도록 하지 않도록 다음 취약지점으로 이동하며 점검하도록 프로그램을 짜보았다. DFS함수의 인자가 많은 것이 아쉬운 부분이지만 다른 방법으로는 떠오르지 않아 함수의 인자 밖에 선택할 수 없었다. weak와 dist를 모두 전역변수로 하면 메모리 낭비와 복사하는 시간이 걸리므로 call by reference를 이용하여 그런 시간을 줄여보았다.

 

채점 결과

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

 

코딩테스트 연습 - 블록 이동하기

[[0, 0, 0, 1, 1],[0, 0, 0, 1, 0],[0, 1, 0, 1, 1],[1, 1, 0, 0, 1],[0, 0, 0, 0, 0]] 7

programmers.co.kr

 

 

풀이

    이 문제는 DFS, BFS 모두 풀이가 가능한 문제이다. 글쓴이는 평소 DFS를 많이 이용했기 때문에 이번에는 BFS를 이용하여 풀어보았다. 로봇이 한 칸을 차지하는 것이 아닌 두 칸을 차지하므로 예외처리를 많이 해야하기 때문에 캐치하지 못한 부분이 있어 일부 테스트 케이스를 통과하지 못할 수도 있다. 나도 이 문제를 풀며 내가 생각하지 못한 부분을 예외처리하지 못하여 약간의 시간이 더 걸렸다. 로봇의 현재 위치에서 생각해야할 부분은 네 가지가 있다.

  1. 다음 위치가 보드를 벗어나는지
  2. 로봇이 차지하는 두 칸 중 하나도 1이 아닌지
  3. 이미 현재 위치를 거쳐갔는지
  4. 회전하는 경우 회전할 때 지나가는 칸이 1이 아닌지

위 네 가지만 예외처리하면 쉽게 풀 수 있는 문제이다. 예외처리만 하면 되는 문제이기 때문에 if, continue 등이 많이 쓰였다.

 

    문제를 풀 때 로봇이 상하좌우로 이동하는 경우, 회전하는 경우를 나누어서 풀었다. 상하좌우로 이동하는 경우는 일반 BFS처럼 풀면 되기 때문에 예외처리가 따로 할 부분이 없었다. 문제는 회전하는 경우였다. 회전하는 경우를 차지하는 두 칸 중에 한 칸이 대각선으로 이동하는 방식으로 생각했다. 그림으로 나타내면 다음과 같다.

위와 같이 이동한다고 할 때 실제로는 2, 4번으로 이동할 수 있지만 1, 3번으로는 이동하지 못한다. 이 부분을 예외처리하기 위해 두 칸의 x좌표나 y좌표 중 하나가 같은 경우에만 올바르게 회전했다고 판단했다. 이 과정을 다른 쪽 칸도 똑같이 진행하였다. 이렇게 회전하는 경우에 갈 수 있는 좌표의 위치를 계산할 수 있다.

 

소스 코드

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

using namespace std;

struct pos{ //x, y좌표를 가지는 구조체
    int x, y;
};

struct element { //큐에 넣을 구조체
    pair<pos,pos> position;
    int depth;
};

bool visit[101][101][101][101]; //방문배열

int dx[4] = {1, 0, 0, -1}; //dx, dy는 상하좌우로 이동
int dy[4] = {0, 1, -1, 0};
int rx[4] = {1, 1, -1, -1}; //rx, ry는 회전
int ry[4] = {1, -1, 1, -1};

int solution(vector<vector<int>> board) {
    int N = board.size();
    pair<pos,pos> robot = {{0, 0}, {0, 1}}; ///로봇의 현재 위치
    queue<element> q; //BFS에 사용될 큐
    
    q.push({robot, 0}); //현재 위치를 큐에 넣음
    visit[robot.first.x][robot.first.y][robot.second.x][robot.second.y] = true; //현재 위치 방문 표시
    visit[robot.second.x][robot.second.y][robot.first.x][robot.first.y] = true; //두 칸을 반대로 했을 때도 방문 표시
    
    while(!q.empty()) { //큐가 빌 때까지
        element e = q.front(); q.pop();
        pair<pos,pos> now = e.position; //현재 위치
        for(int d = 0; d < 4; d++) { //상하좌우 방향으로
            pair<pos,pos> nxt = {{now.first.x + dx[d], now.first.y + dy[d]}, {now.second.x + dx[d], now.second.y + dy[d]}}; //다음 좌표 위치
            if(nxt.first.x < 0 || nxt.first.x >= N) continue; //범위를 벗어나는 경우 건너뛰기
            if(nxt.first.y < 0 || nxt.first.y >= N) continue;
            if(nxt.second.x < 0 || nxt.second.x >= N) continue;
            if(nxt.second.y < 0 || nxt.second.y >= N) continue;
            if(board[nxt.first.x][nxt.first.y] || board[nxt.second.x][nxt.second.y]) continue; //만약 두 칸 중에 하나라도 1인 경우 건너뛰기
            if(visit[nxt.first.x][nxt.first.y][nxt.second.x][nxt.second.y]) continue; //이미 방문한 경우도 건너뛰기
            if(visit[nxt.second.x][nxt.second.y][nxt.first.x][nxt.first.y]) continue; //로봇 왼쪽 오른쪽이 반대로 됐을 경우도 같음
            if((nxt.first.x == N-1 && nxt.first.y == N-1) || (nxt.second.x == N-1 && nxt.second.y == N-1)) { //도착한 경우 방문 횟수 리턴
                return e.depth + 1;
            }
            else { //그렇지 않은 경우 다음 로봇 위치 방문 표시 후 큐에 넣기
                visit[nxt.first.x][nxt.first.y][nxt.second.x][nxt.second.y] = true;
                visit[nxt.second.x][nxt.second.y][nxt.first.x][nxt.first.y] = true;
                q.push({nxt, e.depth + 1});
            }
        }
        
        
        for(int r = 0; r < 4; r++) { //회전하는 경우
            pair<pos,pos> nxt = {{now.first.x + rx[r], now.first.y + ry[r]}, {now.second.x, now.second.y}}; //한 쪽 날개 회전 
            if(nxt.first.x != nxt.second.x && nxt.first.y != nxt.second.y) continue; //만약 한쪽을 대각선으로 회전했을 때 로봇이 차지하는 두 칸이 나란히 있지 않은 경우 건너뛰기
            if(nxt.first.x < 0 || nxt.first.x >= N) continue; //범위를 벗어나는 경우 건너뛰기
            if(nxt.first.y < 0 || nxt.first.y >= N) continue;
            if(nxt.second.x < 0 || nxt.second.x >= N) continue;
            if(nxt.second.y < 0 || nxt.second.y >= N) continue;
            if(board[now.first.x][now.first.y + ry[r]] || board[now.first.x + rx[r]][now.first.y]) continue; //회전시 지나가는 칸이 1인 경우 건너뛰기
            if(board[nxt.first.x][nxt.first.y] || board[nxt.second.x][nxt.second.y]) continue; //차지하는 두 칸중 하나라도 1인 경우 건너뛰기
            if(visit[nxt.first.x][nxt.first.y][nxt.second.x][nxt.second.y]) continue; //이미 방문한 경우 건너뛰기
            if(visit[nxt.second.x][nxt.second.y][nxt.first.x][nxt.first.y]) continue;
            
            if((nxt.first.x == N-1 && nxt.first.y == N-1) || (nxt.second.x == N-1 && nxt.second.y == N-1)) { //종료지점 도착시 리턴 
                return e.depth + 1;
            }
            else { //그렇지 않은 경우 다음 로봇 위치 방문 표시 후 큐에 넣기
                visit[nxt.first.x][nxt.first.y][nxt.second.x][nxt.second.y] = true;
                visit[nxt.second.x][nxt.second.y][nxt.first.x][nxt.first.y] = true;
                q.push({nxt, e.depth + 1});
            }
        }
            
        for(int r = 0; r < 4; r++) { //다른 한 쪽이 회전하는 경우
            pair<pos,pos> nxt = {{now.first.x, now.first.y}, {now.second.x + rx[r], now.second.y + ry[r]}}; //다른 한 쪽 날개 회전
            if(nxt.first.x != nxt.second.x && nxt.first.y != nxt.second.y) continue; //로봇이 차지하는 두 칸이 나란히 있지 않은 경우 리턴
            if(nxt.first.x < 0 || nxt.first.x >= N) continue; //범위를 벗어나는 경우 리턴
            if(nxt.first.y < 0 || nxt.first.y >= N) continue;
            if(nxt.second.x < 0 || nxt.second.x >= N) continue;
            if(nxt.second.y < 0 || nxt.second.y >= N) continue;
            if(board[now.second.x][now.second.y + ry[r]] || board[now.second.x + rx[r]][now.second.y]) continue; //회전시 지나가는 칸이 1인 경우 건너뛰기
            if(board[nxt.first.x][nxt.first.y] || board[nxt.second.x][nxt.second.y]) continue; //차지하는 두 칸중 하나라도 1인 경우 건너뛰기
            if(visit[nxt.first.x][nxt.first.y][nxt.second.x][nxt.second.y]) continue;//이미 방문한 경우 건너뛰기
            if(visit[nxt.second.x][nxt.second.y][nxt.first.x][nxt.first.y]) continue;
            
            if((nxt.first.x == N-1 && nxt.first.y == N-1) || (nxt.second.x == N-1 && nxt.second.y == N-1)) { //종료지점 도착시 리턴
                return e.depth + 1;
            }
            else { //그렇지 않은 경우 다음 로봇 위치 방문 표시 후 큐에 넣기
                visit[nxt.first.x][nxt.first.y][nxt.second.x][nxt.second.y] = true;
                visit[nxt.second.x][nxt.second.y][nxt.first.x][nxt.first.y] = true;
                q.push({nxt, e.depth + 1});
            }
        }
    }
}

같은 코드가 반복되어 비효율적인 부분이 있긴 하지만 프로그램 자체가 길지 않으므로 하나의 함수에 작성하였다.시간의 효율성을 생각해보면 큐 안의 반복문이 4번 반복하는 for문이 3개 있으므로 배열의 크기를 N이라고 할 때 약 N * N * 12번의 반복이 발생하지만 12번의 반복은 10^8이 약 1초 걸린다고 생각할 때 무시해도 될 정도의 아주 작은 시간이므로 그다지 효율성을 떨어뜨리지 않는다고 할 수 있다.

 

채점 결과

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/42892

 

코딩테스트 연습 - 길 찾기 게임

[[5,3],[11,5],[13,3],[3,5],[6,1],[1,3],[8,6],[7,2],[2,2]] [[7,4,6,9,1,8,5,2,3],[9,6,5,8,1,4,3,2,7]]

programmers.co.kr

문제 설명

이 문제는 좌표로 주어지는 노드들을 트리로 이진 트리로 만들어 전위 순회, 후위 순회 결과를 리턴하는 문제이다. 예시는 다음과 같다.

문제 예시

첫 번째 그림과 같이 주어진 좌표를 아래의 그림처럼 이진 트리 구조로 만드는 것이다. 트리를 구성하는 노드들의 규칙은 다음과 같다.

  1. 트리를 구성하는 모든 노드의 x, y 좌표 값은 정수이다.
  2. 모든 노드는 서로 다른 x값을 가진다.
  3. 같은 레벨(level)에 있는 노드는 같은 y 좌표를 가진다.
  4. 자식 노드의 y 값은 항상 부모 노드보다 작다.
  5. 임의의 노드 V의 왼쪽 서브 트리(left subtree)에 있는 모든 노드의 x값은 V의 x값보다 작다.
  6. 임의의 노드 V의 오른쪽 서브 트리(right subtree)에 있는 모든 노드의 x값은 V의 x값보다 크다.

위 예시의 전위 순회와 후위 순회의 결과는 다음과 같이 나온다.

  • 전위 순회 : 7, 4, 6, 9, 1, 8, 5, 2, 3
  • 후위 순회 : 9, 6, 5, 8, 1, 4, 3, 2, 7

 

풀이

이 문제를 풀기 위해서 우선 트리를 구현해야한다. 연결리스트를 이용하여 구현하면 복잡해진다고 판단하여 배열을 이용하였다.

 

1. 트리의 표현

    pair<int,int>로 구성된 배열을 이용하여 first에는 왼쪽 자식, second에는 오른쪽 자식의 번호를 저장하였다. 트리를 사용하기 위해서는 트리를 초기화해야한다. 자식을 가리키는 인덱스 번호가 자기 자신과 같다면 리프노드로 판단하도록 초기화하였다.

 

2. 노드 순서

    입력으로 주어진 노드 정보들을 루트 노드부터 순서대로 정렬해야한다. 이때 주어진 배열의 (인덱스 번호 + 1)이 노드의 번호이기 때문에 주어진 배열 자체를 정렬시키면 안되고 인덱스 번호와 좌표를 유지해야한다. 따라서 각 노드의 번호를 통해 좌표를 쉽게 찾기 위해 map을 사용하였고 전위 순회 방식으로 노드를 정렬하기 위해 정렬을 위한 node라는 배열을 따로 선언하였다.

 

3. 트리 구현

    그 다음에는 정렬된 노드를 순서대로 트리에 붙여나가야한다. 트리를 만드는 find함수를 따로 구현하였다. find 함수는 다음과 같이 동작한다.

  1. 현재 노드의 x좌표보다  추가하는 노드의 x좌표가 작은 경우(왼쪽 자식인 경우)
    • 만약 왼쪽 자식이 자기 자신일 때(리프노드일 때) 왼쪽 자식에 붙인다.
    • 그렇지 않으면 재귀적으로 왼쪽 자식과 현재 노드를 방금 과정과 똑같이 비교한다.
  2. 현재 노드의 x좌표보다 추가하는 노드의 x좌표가 큰 경우(오른쪽 자식인 경우)
    • 만약 오른쪽 자식이 자기 자신일 때(리프노드일 때) 오른쪽 자식에 붙인다.
    • 그렇지 않으면 재귀적으로 오른쪽 자식과 현재 노드를 방금 과정과 똑같이 비교한다.

4. 전위 순회와 후위 순회

    전위 순회는 왼쪽 오른쪽 자식을 탐색하기 전에 자기 자신을 경로에 넣으면 된다. 반대로 후위 순회는 왼쪽 오른쪽 자식을 탐색하고 난 이후에 자기 자신을 경로에 넣으면 된다.

 

소스코드

 

#include <string>
#include <vector>
#include <algorithm>
#include <map>
#include <queue>

using namespace std;

pair<int,int> tree[10001];
map<int,pair<int,int>> m;

vector<int> frontpath; //전위순회 경로
vector<int> endpath; //후위순회 경로

struct edge { //정렬을 위한 구조체
    int idx, x, y;
};

bool cmp(edge a, edge b) { //루트노드부터 순서대로 정렬하는 비교함수
    if(a.y == b.y) {
        return a.x < b.x;
    }
    else
        return a.y > b.y;
}

void find(int now, edge e) { //적절한 위치 찾기
    pair<int,int> nowpos = m[now];
    if(nowpos.first > e.x) { //왼쪽자식인 경우
        if(tree[now].first == now) { //왼쪽 자식 끝인 경우
            tree[now].first = e.idx; //왼쪽 자식에 붙이기
            return;
        }
        else { //끝이 아닌 경우
            find(tree[now].first, e); //재귀적으로 탐색
        }
    }
    else { //오른쪽 자식인 경우
        if(tree[now].second == now) { //오른쪽 자식 끝인 경우
            tree[now].second = e.idx;
            return;
        }
        else {
            find(tree[now].second, e);
        }
    }
    return;
}

void front(int now) { //전위순회
    frontpath.push_back(now); //경로 먼저 추가
    if(tree[now].first != now) { //리프노드가 아니라면
        front(tree[now].first); //왼쪽 자식 탐색
    }
    if(tree[now].second != now) {//리프노드가 아니라면
        front(tree[now].second); //오른쪽 자식 탐색
    }
}

void end(int now) { //후위순회
    if(tree[now].first != now) { //리프노드가 아니라면
        end(tree[now].first); //왼쪽 자식 탐색
    }
    if(tree[now].second != now) { //리프노드가 아니라면
        end(tree[now].second); //오른쪽 자식 탐색
    }
    endpath.push_back(now); //경로 나중에 추가
}

vector<vector<int>> solution(vector<vector<int>> nodeinfo) {
    vector<vector<int>> answer;
    vector<edge> node; //정렬을 위한 배열
    
    for(int i = 1; i <= nodeinfo.size(); i++) { //트리 초기화
        tree[i].first = i; //왼쪽 자식을 자기 자신으로
        tree[i].second = i; //오른쪽 자식을 자기 자신으로
    }
    
    for(int i = 0;i < nodeinfo.size(); i++) { 
        node.push_back({i+1, nodeinfo[i][0], nodeinfo[i][1]}); //정렬을 위한 배열
        m[i+1] = {nodeinfo[i][0], nodeinfo[i][1]}; //각 노드 번호별 x, y좌표 map에 저장
    }
    sort(node.begin(), node.end(), cmp); //y좌표가 큰순, x좌표가 작은순으로 정렬
    
    int root = node[0].idx; //루트 인덱스 번호
    
    for(int i = 1; i < node.size(); i++) {
        edge now = node[i]; //현재 노드
        find(root, now); //자식으로 붙이기
    }
    
    front(root); //전위순회
    end(root); //후위순회
    
    
    answer.push_back(frontpath);
    answer.push_back(endpath);
    
    return answer;
}

 

결과

 

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