문제

  • 문제 링크
  • 문제 설명: 검색어에 가장 잘 맞는 웹페이지를 보여주기 위해 아래와 같은 규칙으로 검색어에 대한 웹페이지의 매칭점수를 계산 하는 것
    • 한 웹페이지에 대해서 기본점수, 외부 링크 수, 링크점수, 그리고 매칭점수를 구할 수 있다.
    • 한 웹페이지의 기본점수는 해당 웹페이지의 텍스트 중, 검색어가 등장하는 횟수이다. (대소문자 무시)
    • 한 웹페이지의 외부 링크 수는 해당 웹페이지에서 다른 외부 페이지로 연결된 링크의 개수이다.
    • 한 웹페이지의 링크점수는 해당 웹페이지로 링크가 걸린 다른 웹페이지의 기본점수 ÷ 외부 링크 수의 총합이다.
    • 한 웹페이지의 매칭점수는 기본점수와 링크점수의 합으로 계산한다.
  • 검색어 word와 웹페이지의 HTML 목록인 pages가 주어졌을 때, 매칭점수가 가장 높은 웹페이지의 index를 구하라. 만약 그런 웹페이지가 여러 개라면 그중 번호가 가장 작은 것을 반환하는 함수를 작성
  • 입력
    • pages는 길이는 1 이상 20 이하
    • 한 웹페이지 문자열의 길이는 1 이상 1,500 이하
    • 한 웹페이지의 url은 HTML의 <head> 태그 내에 <meta> 태그의 값으로 주어진다.
      • <meta property="og:url" content="https://careers.kakao.com/index" />
    • 한 웹페이지에서 모든 외부 링크는 <a href="https://URL"></a>
    • <a> 내에 다른 attribute가 주어지는 경우는 없으며 항상 href로 연결할 사이트의 url만 포함된다.
    • word의 길이는 1 이상 12 이하
    • 검색어를 찾을 때, 대소문자 구분은 무시하고 찾는다.
    • 검색어는 단어 단위로 비교하며, 단어와 완전히 일치하는 경우에만 기본 점수에 반영한다.
    • 단어는 알파벳을 제외한 다른 모든 문자로 구분한다.
  • 출력: 매칭 점수가 가장 높은 웹페이지의 인덱스

풀이

  • 먼저 각 웹 페이지마다 외부 링크를 찾아내서 모두 저장하는데 이 과정에서 페이지의 텍스트를 검사해서 기본 점수와 URL을 찾아낸다.
    • map<string, int> 자료형으로 URL 별 페이지 인덱스를 별도로 저장한다.
  • 페이지의 외부 링크를 돌면서 URL이 현재 pages 배열에 있는 페이지의 것이면 (기본 점수 / 외부 링크 수) 를 계산해서 외부 링크 페이지의 점수에 더해준다.
  • 점수 내림차순, 인덱스 오름차순으로 정렬 후 첫번째 원소의 인덱스를 반환
  • meta 태그로 페이지 URL을 탐색할 때 property와 같은 모든 속성에 대해서도 키워드를 주고 찾아야 함
    • <meta content="~"> 와 같은 것은 안됌
  • 검색어 탐색에서 대소문자를 무시하므로 맨 처음에 페이지의 모든 텍스트를 소문자로 변경하는 것이 이후 텍스트 처리하기 편함
    • transform(html.begin(), html.end(), ::tolower)
  • 외부 링크를 찾고나서 웹페이지 텍스트를 찾을 때는 body 안에서만 해도 유효하며, 알파벳 외에 모든 문자를 공백으로 치환하는 것이 좋음
    • replace_if(body.begin(), body.end(), 알파벳 아니면 true/맞으면 false 함수, 공백)
  • 정답 코드
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <sstream>
using namespace std;

const string urlPrefix = "https://";

int solution(string word, vector<string> pages) {
    map<string, int> m; // (url, page index)
    vector<string> links[pages.size()];
    vector<int> numWords(pages.size(), 0);
    vector<pair<double, int>> totalScore;
    // 검색 단어 소문자로 치환
    transform(word.begin(), word.end(), word.begin(), ::tolower);
    for (int i = 0; i < pages.size(); i++) {
        // 페이지 텍스트 모두 소문자로 치환
        string& html = pages[i];
        transform(html.begin(), html.end(), pages[i].begin(), ::tolower);
        // 페이지 URL 탐색
        string token = "<meta property=\"og:url\" content=\"" + urlPrefix;
        int tokenStart = html.find(token);
        int tokenEnd = html.find("\"", tokenStart + token.size());
        int urlStart = tokenStart + token.size() - urlPrefix.size();
        m[html.substr(urlStart, tokenEnd - urlStart)] = i;
        // body 텍스트 분리
        int bodyStart = html.find("<body>") + 6;
        int bodyEnd = html.find("</body>") - 1;
        string body = html.substr(bodyStart, bodyEnd - bodyStart);
        // 외부 링크 탐색
        token = "<a href=\"" + urlPrefix;
        tokenStart = body.find(token);
        while (tokenStart != string::npos) {
            tokenEnd = body.find("\"", tokenStart + token.size());
            urlStart = tokenStart + token.size() - urlPrefix.size();
            links[i].push_back(body.substr(urlStart, tokenEnd - urlStart));
            tokenStart = body.find(token, urlStart + links[i].back().size());
        }
        // 알파벳 외에 모두 공백으로 치환 후 단어 탐색
        replace_if(body.begin(), body.end(), [](auto& ch) { return !isalpha(ch); }, ' ');
        istringstream iss(body);
        while (getline(iss, token, ' '))
            if (token == word) numWords[i]++;
        totalScore.push_back({numWords[i], i});
    }
    
    // 외부 링크마다 pages에 존재하는 페이지면 링크 점수를 계산해서 더해줌
    for (int i = 0; i < pages.size(); i++) {
        int linkSize = links[i].size();
        for (string& l : links[i])
            if (m.find(l) != m.end())
                totalScore[m[l]].first += (double)numWords[i] / (double)linkSize;
    }
    
    // 매칭 점수 내림차순, 인덱스 오름차순으로 정렬
    sort(totalScore.begin(), totalScore.end(), [&](auto& a, auto& b) mutable {
        if (a.first == b.first) return a.second < b.second;
        return a.first > b.first;
    });
    return totalScore[0].second;
}

문제

  • 문제 링크
  • 문제 설명: 카카오 프렌즈를 두 팀으로 나누고, 각 팀이 같은 곳을 다른 순서로 방문하도록 해서 먼저 순회를 마친 팀이 승리하는 것. 라이언은 방문할 곳의 2차원 좌표 값을 구하고 각 장소를 이진트리의 노드가 되도록 구성한 후, 순회 방법을 힌트로 주어 각 팀이 스스로 경로를 찾도록 할 계획. 라이언은 아래와 같은 특별한 규칙으로 트리 노드들을 구성한다.
    • 트리를 구성하는 모든 노드의 x, y 좌표 값은 정수이다.
    • 모든 노드는 서로 다른 x값을 가진다.
    • 같은 레벨(level)에 있는 노드는 같은 y 좌표를 가진다.
    • 자식 노드의 y 값은 항상 부모 노드보다 작다.
    • 임의의 노드 V의 왼쪽 서브 트리(left subtree)에 있는 모든 노드의 x값은 V의 x값보다 작다.
    • 임의의 노드 V의 오른쪽 서브 트리(right subtree)에 있는 모든 노드의 x값은 V의 x값보다 크다.

  • 이진트리를 구성하는 노드들의 좌표가 담긴 배열 nodeinfo가 매개변수로 주어질 때, 노드들로 구성된 이진트리를 전위 순회, 후위 순회한 결과를 2차원 배열에 순서대로 담아 반환하는 함수를 작성
  • 입력
    • nodeinfo의 길이는 1 이상 10,000 이하
    • nodeinfo[i] 는 i + 1번 노드의 좌표이며, [x축 좌표, y축 좌표] 순으로 들어있다.
    • 모든 노드의 좌표 값은 0 이상 100,000 이하인 정수
    • 트리의 깊이가 1,000 이하인 경우만 입력으로 주어진다.
  • 출력: 전위 순회, 후위 순회 결과를 저장한 배열

풀이

  • 이진 트리는 비단말 노드가 자식 노드를 최대 2개만 가질 수 있기 때문에 y좌표 내림차순, x좌표 오름차순으로 정렬한 후 값이 큰 y부터 차례대로 삽입하면 x값을 비교하면서 위의 조건들이 자연스레 만족되면서 위 그림과 같이 이진트리가 만들어짐
  • 즉, 잘못된 순서로 입력되어지는 경우가 없기 때문에 다음 노드부터 x값 기준으로 부모보다 작으면 왼쪽 자식으로 크면 오른쪽 자식으로 보낸다. 둘 다 자식이 없으면 그 때는 자식을 생성한다.
    • y값이 같은게 중간 레벨에 1개 있거나 3개 이상 있는 경우는 없으므로 제대로 동작
  • 맨 처음에 좌표마다 노드 번호를 저장 map<pii, int>
  • 주의할 점: 전위 순회, 후위 순회는 일반적으로 알고 있는 순회가 아닌 그림의 순서를 참고해야 함.
  • 정답 코드
#include <string>
#include <vector>
#include <algorithm>
#include <map>
#define X first
#define Y second
using namespace std;
typedef pair<int,int> pii;

map<pii, int> m;

struct Node {
    pii c;
    Node* l;
    Node* r;
    
    Node(pii c): c(c), l(nullptr), r(nullptr) {}
    
    void insert(pii p) {
        if (c.X > p.X) {
            if (l) l->insert(p);
            else l = new Node(p);
        } else {
            if (r) r->insert(p);
            else r = new Node(p);
        }
    }
    
    void preorder(vector<int>& v) {
        v.push_back(m[c]);
        if (l) l->preorder(v);
        if (r) r->preorder(v);
    }
    
    void postorder(vector<int>& v) {
        if (l) l->postorder(v);
        if (r) r->postorder(v);
        v.push_back(m[c]);
    }
};

vector<vector<int>> solution(vector<vector<int>> nodeinfo) {
    for (int i = 0; i < nodeinfo.size(); i++)
        m[{nodeinfo[i][0], nodeinfo[i][1]}] = i+1;
    
    sort(nodeinfo.begin(), nodeinfo.end(), [&](auto& a, auto& b) mutable {
        if (a[1] == b[1]) return a[0] < b[0];
        return a[1] > b[1];
    });
    
    // 이진 트리 생성
    Node* root = new Node({nodeinfo[0][0], nodeinfo[0][1]});
    for (int i = 1; i < nodeinfo.size(); i++)
        root->insert({nodeinfo[i][0], nodeinfo[i][1]});
    vector<int> v1, v2;
    root->preorder(v1); // 전위 순회
    root->postorder(v2); // 후위 순회
    return {v1, v2};
}

 

문제

  • 문제 링크
  • 문제 설명: 로봇은 2 x 1 크기의 로봇으로 "무지"는 "0"과 "1"로 이루어진 N x N 크기의 지도에서 2 x 1 크기인 로봇을 움직여 (N, N) 위치까지 이동 할 수 있도록 프로그래밍을 하려고 합니다. 로봇이 이동하는 지도는 가장 왼쪽, 상단의 좌표를 (1, 1)로 하며 지도 내에 표시된 숫자 "0"은 빈칸을 "1"은 벽을 나타냅니다. 로봇은 벽이 있는 칸 또는 지도 밖으로는 이동할 수 없습니다. 좌표 (1, 1) 위치에서 가로방향으로 놓여있는 상태로 시작하며, 앞뒤 구분없이 움직일 수 있습니다. 로봇은 90도씩 회전할 수 있습니다. 단, 로봇이 차지하는 두 칸 중, 어느 칸이든 축이 될 수 있지만, 회전하는 방향(축이 되는 칸으로부터 대각선 방향에 있는 칸)에는 벽이 없어야 합니다. 로봇이 한 칸 이동하거나 90도 회전하는 데는 걸리는 시간은 정확히 1초 입니다. "0"과 "1"로 이루어진 지도인 board가 주어질 때, 로봇이 (N, N) 위치까지 이동하는데 필요한 최소 시간을 반환하는 함수를 작성

  • 입력
    • board의 한 변의 길이는 5 이상 100 이하
    • 로봇이 처음에 놓여 있는 칸 (1, 1), (1, 2)는 항상 0으로 주어짐
    • 로봇이 항상 목적지에 도착할 수 있는 경우만 입력으로 주어짐
  • 출력: 최소 시간

풀이

  • 로봇을 상하좌우로 이동하거나 회전하는 경우 1초가 지나기 때문에 로봇의 위치를 노드로 저장해서 BFS로 구현
  • 로봇의 두 좌표 중 하나라도 (N, N)에 도달하면 그 때 걸린 시간이 최소 시간
  • 로봇의 위치를 어떻게 나타낼 것인가? 두 좌표 중 1개(x,y)와 그 좌표에서 다음 좌표를 가리키는 방향(상/하/좌/우 총 4가지)
    • 두 좌표를 저장하는 것보다 회전/이동이 쉽고 방문 체크나 벽 체크하기 용이함
    • 로봇의 좌표를 c1과 c2라고 할 때 방향(dir)에 따라 좌표는 아래와 같음

  • 좌표의 변화? (dir 은 d배열의 인덱스)
    • 움직이는 경우 => c1 에만 변화를 주고 dir은 그대로
    • 회전하는 경우 => c1, c2 각각에 대해 다음과 같이 2가지 종류로 회전
      • dir=0,2 이면 dir=1,3 으로 호출
      • dir=1,3 이면 dir=0,2 으로 호출
    • 회전한 후의 좌표를 (ny,nx)라 할 때 돌리는 방향에 벽이 없어야 함
      • dir=0,2
        • c1이 축이면 (ny, c2.X) 와 회전 후 좌표 모두 벽이 아니고 범위 안에 있어야 함
        • c2가 축이면 (ny, c1.X) 와 회전 후 좌표 모두 벽이 아니고 범위 안에 있어야 함
      • dir=1,3
        • c1이 축이면 (c2.Y, nx) 와 회전 후 좌표 모두 벽이 아니고 범위 안에 있어야 함
        • c2가 축이면 (c1.Y, nx) 와 회전 후 좌표 모두 벽이 아니고 범위 안에 있어야 함
  • 정답 코드
#include <string>
#include <vector>
#include <queue>
#include <cstring>
#define Y first
#define X second
using namespace std;
typedef pair<int,int> pii;
const int d[5] = {0, -1, 0, 1, 0};

vector<vector<int>> B;
int N;

bool isInRange(pii c) {
    if (c.Y < 0 or c.Y >= N or c.X < 0 or c.X >= N) return false;
    return !B[c.Y][c.X];
}

int bfs() {
    int ret = 0;
    bool visited[100][100][4];
    memset(visited, 0, sizeof(visited));
    queue<pair<int, pii>> q; // (dir, (y,x))
    q.push({2, {0,0}});
    visited[0][0][2] = true;
    while (!q.empty()) {
        int sz = q.size();
        while (sz--) { // depth 계산을 정확하게 하기 위해 딱 존재하는 노드만큼만 수행
            int dir = q.front().first;
            pii c1 = q.front().second;
            pii c2 = {c1.Y + d[dir], c1.X + d[dir+1]};
            q.pop();
            if ((c1.Y == N-1 and c1.X == N-1) or (c2.Y == N-1 and c2.X == N-1))
                return ret;
            
            // 상하좌우 이동: (y,x)만 변화를 주고 dir은 그대로 유지
            for (int i = 0; i < 4; i++) {
                pii nc1 = {c1.Y + d[i], c1.X + d[i+1]};
                pii nc2 = {nc1.Y + d[dir], nc1.X + d[dir+1]};
                if (isInRange(nc1) and isInRange(nc2) and !visited[nc1.Y][nc1.X][dir]) {
                    q.push({dir, nc1});
                    visited[nc1.Y][nc1.X][dir] = true;
                }
            }
            
            // 회전: dir에 따라 회전할 방향과 좌표 검사를 함
            int newDir[2] = {(dir&1) ? 0 : 1, (dir&1) ? 2 : 3};
            for (int i = 0; i < 2; i++) {
                int rd = newDir[i];
                pii nc2 = {c1.Y + d[rd], c1.X + d[rd+1]};
                // c1 을 축으로 회전
                if (isInRange(nc2)) {
                    if (((dir & 1) and isInRange({c2.Y, nc2.X})) or
                       (!(dir & 1) and isInRange({nc2.Y, c2.X}))) {
                        if (!visited[c1.Y][c1.X][rd]) {
                            q.push({rd, c1});
                            visited[c1.Y][c1.X][rd] = true;
                        }
                    }
                }
                
                // c2 를 축으로 회전
                pii nc1 = {c2.Y + d[rd], c2.X + d[rd+1]};
                if (isInRange(nc1)) {
                    if (((dir & 1) and isInRange({c1.Y, nc1.X})) or
                       (!(dir & 1) and isInRange({nc1.Y, c1.X}))) {
                        if (!visited[c2.Y][c2.X][rd]) {
                            q.push({rd, c2});
                            visited[c2.Y][c2.X][rd] = true;
                        }
                    }
                }
            }
        }
        ret++;
    }
    return ret;
}

int solution(vector<vector<int>> board) {
    N = board.size();
    B = board;
    return bfs();
}

문제

  • 문제 링크
  • 문제 설명: 레스토랑의 구조는 완전히 동그란 모양이고 외벽의 총 둘레는 n미터이며, 외벽의 몇몇 지점은 추위가 심할 경우 손상될 수도 있는 취약한 지점들이 있어 친구들을 보내 점검하고자 한다. 빠른 공사를 위해 점검 시간을 1시간으로 제한했다. 외벽의 길이 n, 취약 지점의 위치가 담긴 배열 weak, 각 친구가 1시간 동안 이동할 수 있는 거리가 담긴 배열 dist가 매개변수로 주어질 때, 취약 지점을 점검하기 위해 보내야 하는 친구 수의 최소값을 반환하는 함수를 작성
    • 레스토랑의 정북 방향 지점을 0으로 나타내며, 취약 지점의 위치는 정북 방향 지점으로부터 시계 방향으로 떨어진 거리
    • 친구들은 출발 지점부터 시계, 혹은 반시계 방향으로 외벽을 따라서만 이동
  • 입력
    • n은 1 이상 200 이하인 자연수
    • weak의 길이는 1 이상 15 이하
    • 취약 지점의 위치는 오름차순으로 정렬
    • weak의 원소는 0 이상 n - 1 이하인 정수
    • dist의 길이는 1 이상 8 이하
    • dist의 원소는 1 이상 100 이하인 자연수
  • 출력: 친구 수의 최솟값
    • 친구들을 모두 투입해도 취약 지점을 전부 점검할 수 없는 경우에는 -1을 반환
n weak dist result
12 [1, 5, 6, 10] [1, 2, 3, 4] 2
12 [1, 3, 4, 9, 10] [3, 5, 7] 1

풀이

  • dist 배열을 내림차순으로 정렬
  • 각 친구마다 weak의 각 위치에 배치해보면서 시계방향, 반시계방향으로 점검하는 경우를 모두 시도해서 모든 취약 지점이 점검되는 경우 최소 친구 수를 탐색
  • dist 배열의 크기가 최대 8이고, weak 배열의 크기가 최대 15이기 때문에 매 취약 지점마다 각 친구를 배치하는 경우를 모두 탐색할 수 있다고 생각해서 완전 탐색으로 풀었는데 시간 초과 발생
    • 현재 배치될 친구 인덱스와 점검한 weak 목록에 대해 부분 문제가 발생하므로 DP로 최적화
  • 정답 코드
#include <cstring>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

int N;
const int INF = 1e9;
int dp[8][1<<15];

int dfs(int pos, vector<int>& dist, vector<int>& weak, int checked) {
    // 모든 취약지점이 점검되었다면 0을 반환
    if (__builtin_popcount(checked) == weak.size()) return 0;
    if (pos == dist.size()) return INF; // 모든 친구가 배치되었으면 INF
    int& ret = dp[pos][checked];
    if (ret != -1) return ret;
    ret = INF;
    for (int i = 0; i < weak.size(); i++) {
        if (checked & (1 << i)) continue;
        // 시계 방향
        int newChecked = checked;
        int maxPos = weak[i] + dist[pos];
        for (int j = i; ; j++) {
            if (j == weak.size()) { // 끝에 도달한 경우 첫번째 취약 지점으로 인덱스 갱신
                j = 0;
                // 최대 이동 가능 지점도 갱신. 정북 지점을 지나지 않으면 -1
                maxPos = weak[i] + dist[pos] > N ? maxPos % N : -1;
            }
            // 아직 점검되지 않았고 이동 반경 내에 있으면 점검
            if (!(newChecked & (1 << j)) and weak[j] <= maxPos)
                newChecked |= (1 << j);
            else break;
        }
        ret = min(ret, dfs(pos+1, dist, weak, newChecked) + 1);
        // 반시계 방향
        newChecked = checked;
        int minPos = weak[i] - dist[pos];
        for (int j = i; ; j--) {
            if (j < 0) { // 첫번째 지점에 도달한 경우 마지막 취약 지점으로 인덱스 갱신
                j = weak.size()-1;
                // 최대 이동 가능 지점도 갱신. 정북 지점을 지나지 않으면 N
                minPos = weak[i] < dist[pos] ? minPos + N : N;
            }
            if (!(newChecked & (1 << j)) and weak[j] >= minPos)
                newChecked |= (1 << j);
            else break;
        }
        ret = min(ret, dfs(pos+1, dist, weak, newChecked) + 1);
    }
    return ret;
}

int solution(int n, vector<int> weak, vector<int> dist) {
    N = n;
    sort(dist.begin(), dist.end(), [&](int a, int b) mutable { return a > b; });
    memset(dp, -1, sizeof(dp));
    int ret = dfs(0, dist, weak, 0);
    return ret == INF ? -1 : ret;
}

+ Recent posts