문제

  • 문제 링크
  • 문제 설명: 1초마다 회전하는 음식판이 있다. 1번부터 N번까지 음식 번호가 증가하는 순서대로 음식을 섭취할 때, K초에 네트워크 장애가 발생한다면 네트워크 정상화 후 먹어야하는 음식의 번호를 반환하는 함수를 작성하라.
    • 음식을 모두 섭취했다면 가장 가까운 음식이 다음 대상이 된다.
    • 마지막 번호의 음식을 섭취한 후에는 회전판에 의해 다시 1번 음식이 무지 앞으로 온다.
  • 입력
    • food_times 는 각 음식을 모두 먹는데 필요한 시간이 음식의 번호 순서대로 들어있는 배열이다.
    • k 는 방송이 중단된 시간을 나타낸다.
    • food_times 의 길이는 1 이상 200,000 이하이다.
    • food_times 의 원소는 1 이상 100,000,000 이하의 자연수이다.
    • k는 1 이상 2 x 10^13 이하의 자연수이다.
  • 출력: 네트워크 정상화 후 먹어야하는 음식 번호
    • 만약 더 섭취해야 할 음식이 없다면 -1을 반환하면 된다.
food_times k result
[3, 1, 2] 5 1

풀이

  • food_times의 길이가 2*10^5 이기 때문에 O(N) 또는 O(NlogN) 만에 해결해야 함
  • 먼저 모든 음식들을 우선순위 큐에 삽입
    • 원소 := (섭취시간, 인덱스)
    • 우선순위 큐는 가장 큰 값을 top() 으로 반환하므로 -1을 곱해서 섭취시간이 가장 작은 것을 O(1)에 얻을 수 있도록 한다.
  • 섭취 시간에 대해 다음과 같은 변수를 사용
    • food_items[i] := i번째 음식이 섭취하는데 필요한 시간
    • totalTime := 지금까지 총 걸린 시간
    • t := 현재 음식 하나를 먹을 때 섭취 시간 (최초 최소 섭취 시간)
    • n := 현재 남은 음식 개수
    • tsum := 지금까지 t가 누적된 섭취 시간
  • 큐에서 총 섭취 시간이 k이하이면 음식을 섭취하고 시간을 갱신
    • tsum += t; (최초 tsum = 0)
    • 섭취 시간 <= tsum 인 음식들만 섭취 
    • 1번 먹는데 걸린 시간: 현재 음식 하나를 먹을 때 섭취 시간 * 남은 음식 개수 = t * n
    • 1번 먹고난 후 시간: totalTime + (t * n)
    • t = -pq.top() - tsum; // 우선순위 큐는 가장 큰 값을 top 에 두기 때문에 -1을 곱해서 작은값을 위로
    • n = pq.size()
  • 총 섭취 시간이 k 보다 크면 섭취 시간을 줄일 것
    • 섭취 시간이 이미 1이면 break
    • t = (k - totalTime) / n;
    • ㄴ 이 결과가 1보다 작으면 k == totalTime 이기 때문에 네트워크 장애가 발생한 시점이므로 break
  • 남은 음식들 중 k - totalTime 번째 음식 번호를 반환
  • 정답 코드
#include <string>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef pair<ll,int> pli;

int solution(vector<int> food_times, long long k) {
    priority_queue<pli> pq;
    ll n = food_times.size(); // 현재 남은 음식 개수
    ll totalTime = 0; // 지금까지 총 걸린 시간
    ll t = 1e9; // 현재 음식 하나를 먹을 때 섭취 시간
    ll tsum = 0; // 지금까지 t가 누적된 섭취 시간
    for (int i = 0; i < n; i++) {
        pq.push({-food_times[i], i+1});
        if (food_times[i] < t) t = food_times[i];
    }
    
    while (!pq.empty()) {
        if (totalTime + t * n > k) {
            if (t == 1) break;
            t = (k - totalTime) / n; // 딱 k까지 필요한 섭취 시간 구하기
            if (t < 1) break; // k == totalTime 이면 네트워크 장애가 발생하니 종료
        }
        
        tsum += t; // 시간 누적
        while (-pq.top().first <= tsum) {
            pq.pop();
            if (pq.empty()) return -1; // 이미 다먹었으면 -1
        }
        totalTime += t * n; // 총 시간 갱신
        t = -pq.top().first - tsum; // 음식 하나당 섭취 시간 갱신
        n = pq.size(); // 남은 음식 개수 갱신
    }
    
    vector<int> v;
    while(!pq.empty()) { v.push_back(pq.top().second); pq.pop(); }
    sort(v.begin(), v.end()); // 음식 번호 순으로 정렬
    return v.size() < k-totalTime ? -1 : v[k-totalTime];
}

 

문제

  • 문제 링크
  • 문제 설명: 가사에 사용된 모든 단어들이 담긴 배열 words와 찾고자 하는 키워드가 담긴 배열 queries가 주어질 때, 각 키워드 별로 매치된 단어가 몇 개인지 순서대로 배열에 담아 반환하는 함수 작성
  • 입력
    • 가사 단어 제한 사항
      • words의 길이(가사 단어의 개수)는 2 이상 100,000 이하입니다.
      • 각 가사 단어의 길이는 1 이상 10,000 이하로 빈 문자열인 경우는 없습니다.
      • 전체 가사 단어 길이의 합은 2 이상 1,000,000 이하입니다.
      • 가사에 동일 단어가 여러 번 나올 경우 중복을 제거하고 words에는 하나로만 제공됩니다.
      • 각 가사 단어는 오직 알파벳 소문자로만 구성되어 있으며, 특수문자나 숫자는 포함하지 않는 것으로 가정합니다.
    • 검색 키워드 제한 사항
      • queries의 길이(검색 키워드 개수)는 2 이상 100,000 이하입니다.
      • 각 검색 키워드의 길이는 1 이상 10,000 이하로 빈 문자열인 경우는 없습니다.
      • 전체 검색 키워드 길이의 합은 2 이상 1,000,000 이하입니다.
      • 검색 키워드는 중복될 수도 있습니다.
      • 각 검색 키워드는 오직 알파벳 소문자와 와일드카드 문자인 '?' 로만 구성되어 있으며, 특수문자나 숫자는 포함하지 않는 것으로 가정합니다.
      • 검색 키워드는 와일드카드 문자인 '?'가 하나 이상 포함돼 있으며, '?'는 각 검색 키워드의 접두사 아니면 접미사 중 하나로만 주어집니다.
  • 출력: 키워드별 매치된 단어 개수 배열
words queries result
["frodo", "front", "frost", "frozen", "frame", "kakao"] ["fro??", "????o", "fr???", "fro???", "pro?"] [3, 2, 4, 1, 0]

풀이

  • 전체 가사단어/검색키워드 길이의 합이 100만이 넘으므로 naive한 방법으로 탐색 시 시간 초과가 발생
  • 트라이로 문자열을 탐색하되, 각 노드(문자1개)는 이후 남은 길이에 대해 단어의 개수를 map에 저장 (있는 그대로 탐색할 경우 시간초과)
  • 와일드카드가 접두사/접미사에만 등장하므로 접두사 트라이와 접미사 트라이를 둘 다 구현해서 와일드 카드가 앞에 오면 접미사 트라이에서 탐색하고 와일드 카드가 뒤에 오면 접두사 트라이에서 탐색.
  • 전처리할 때 검색 키워드 길이의 합 100만만큼 비용이 소모된다. 매칭 개수 찾는 것은 전처리보다 적게 듬
  • 정답 코드
#include <cstring>
#include <string>
#include <vector>
#include <unordered_map>
#include <algorithm>
using namespace std;

struct TrieNode {
    bool terminal;
    TrieNode* children[26];
    unordered_map<int, int> m; // (len, cnt)
    
    TrieNode(): terminal(false) {
         memset(children, 0, sizeof(children));
    }
    
    void insert(int pos, const string& s) {
        if (pos < s.size()) {
            int idx = s[pos]-'a';
            if (children[idx] == 0)
                children[idx] = new TrieNode();
            children[idx]->insert(pos+1, s);
        } else terminal = true;
        m[s.size() - pos]++; // 남은 길이마다 단어 수 증가
    }
    
    int search(int pos, const string& s) {
        if (s[pos] == '?') return m[s.size() - pos]; // 다음 노드에 대해 ?이면 단어 수 반환
        else if (children[s[pos]-'a'])
            return children[s[pos]-'a']->search(pos+1, s);
        return 0;
    }
};

vector<int> solution(vector<string> words, vector<string> queries) {
    TrieNode* prefixTrie = new TrieNode();
    TrieNode* suffixTrie = new TrieNode();
    for (auto& w : words) {
        prefixTrie->insert(0, w);
        reverse(w.begin(), w.end());
        suffixTrie->insert(0, w);
    }
    vector<int> answer;
    for (auto& q : queries) {
        if (q[0] == '?') { // 접두사에 와일드카드가 있으면 접미사 트라이에서 탐색
            reverse(q.begin(), q.end());
            answer.push_back(suffixTrie->search(0, q));
        } else { // 접미사에 와일드카드가 있으면 접두사 트라이에서 탐색
            answer.push_back(prefixTrie->search(0, q));
        }
    }
    return answer;
}

 

문제

  • 문제 링크
  • 문제 설명: 검색어에 가장 잘 맞는 웹페이지를 보여주기 위해 아래와 같은 규칙으로 검색어에 대한 웹페이지의 매칭점수를 계산 하는 것
    • 한 웹페이지에 대해서 기본점수, 외부 링크 수, 링크점수, 그리고 매칭점수를 구할 수 있다.
    • 한 웹페이지의 기본점수는 해당 웹페이지의 텍스트 중, 검색어가 등장하는 횟수이다. (대소문자 무시)
    • 한 웹페이지의 외부 링크 수는 해당 웹페이지에서 다른 외부 페이지로 연결된 링크의 개수이다.
    • 한 웹페이지의 링크점수는 해당 웹페이지로 링크가 걸린 다른 웹페이지의 기본점수 ÷ 외부 링크 수의 총합이다.
    • 한 웹페이지의 매칭점수는 기본점수와 링크점수의 합으로 계산한다.
  • 검색어 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};
}

 

+ Recent posts