Kakao - 보석 쇼핑

Hyung Jun·2021년 1월 20일
0

Algorithm

목록 보기
8/14
post-thumbnail

보석쇼핑

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

Description

개발자 출신으로 세계 최고의 갑부가 된 어피치는 스트레스를 받을 때면 이를 풀기 위해 오프라인 매장에 쇼핑을 하러 가곤 합니다.
어피치는 쇼핑을 할 때면 매장 진열대의 특정 범위의 물건들을 모두 싹쓸이 구매하는 습관이 있습니다.
어느 날 스트레스를 풀기 위해 보석 매장에 쇼핑을 하러 간 어피치는 이전처럼 진열대의 특정 범위의 보석을 모두 구매하되 특별히 아래 목적을 달성하고 싶었습니다.
진열된 모든 종류의 보석을 적어도 1개 이상 포함하는 가장 짧은 구간을 찾아서 구매
예를 들어 아래 진열대는 4종류의 보석(RUBY, DIA, EMERALD, SAPPHIRE) 8개가 진열된 예시입니다.
진열대 번호 1 2 3 4 5 6 7 8
보석 이름 DIA RUBY RUBY DIA DIA EMERALD SAPPHIRE DIA
진열대의 3번부터 7번까지 5개의 보석을 구매하면 모든 종류의 보석을 적어도 하나 이상씩 포함하게 됩니다.
진열대의 3, 4, 6, 7번의 보석만 구매하는 것은 중간에 특정 구간(5번)이 빠지게 되므로 어피치의 쇼핑 습관에 맞지 않습니다.
진열대 번호 순서대로 보석들의 이름이 저장된 배열 gems가 매개변수로 주어집니다. 이때 모든 보석을 하나 이상 포함하는 가장 짧은 구간을 찾아서 return 하도록 solution 함수를 완성해주세요.
가장 짧은 구간의 시작 진열대 번호와 끝 진열대 번호를 차례대로 배열에 담아서 return 하도록 하며, 만약 가장 짧은 구간이 여러 개라면 시작 진열대 번호가 가장 작은 구간을 return 합니다.
[제한사항]
gems 배열의 크기는 1 이상 100,000 이하입니다.
gems 배열의 각 원소는 진열대에 나열된 보석을 나타냅니다.
gems 배열에는 1번 진열대부터 진열대 번호 순서대로 보석이름이 차례대로 저장되어 있습니다.
gems 배열의 각 원소는 길이가 1 이상 10 이하인 알파벳 대문자로만 구성된 문자열입니다.

Example

gems result
["DIA", "RUBY", "RUBY", "DIA", "DIA", "EMERALD", "SAPPHIRE", "DIA"][3, 7]
["AA", "AB", "AC", "AA", "AC"][1, 3]
["XYZ", "XYZ", "XYZ"][1, 1]
["ZZZ", "YYY", "NNNN", "YYY", "BBB"][1, 5]

Code

#include <bits/stdc++.h>

using namespace std;

bool candidate_check(map<int, int>& current_gem, int kind){

    if (current_gem.size() == kind)
        return true;
    return false;
}

vector<int> solution(vector<string> gems) {
    vector<int> answer;
    map<string, int> gems_map;
    int i = 1;
    for (string gem : gems){
        if(!gems_map[gem])
            gems_map[gem] = i++;
    }
    int kind = i-1;
    int start=0, end=0;
    int len = gems.size();
    map<int, int> current_gem;

    vector<vector<int> > candidate_vect;

    

    while (end <= len && start <= end)
    {
        if(end == 0){
            end += 1;
            if(!current_gem[gems_map[gems[end-1]]])
                current_gem[gems_map[gems[end-1]]] = 1;
            else
                current_gem[gems_map[gems[end-1]]] += 1;
            continue;
        }
        if(candidate_check(current_gem, kind) == true){ // start move
            candidate_vect.push_back({start, end});
            if(start > 0){
                current_gem[gems_map[gems[start-1]]] -= 1;
                if (current_gem[gems_map[gems[start-1]]] == 0){
                    auto it =  current_gem.find(gems_map[gems[start-1]]);
                    current_gem.erase(it);
                }
            }
            start += 1;
        }
        else{ // end move
            if (end < len){
                end += 1;
                if(!current_gem[gems_map[gems[end-1]]])
                    current_gem[gems_map[gems[end-1]]] = 1;
                else
                    current_gem[gems_map[gems[end-1]]] += 1;
            }
            else if (end == len)
                break;
        }
    }
    int min_len = 100000;
    for(auto vect : candidate_vect){
        
        if(vect[1] - vect[0] < min_len){
            min_len = vect[1] - vect[0];
            answer = {vect[0], vect[1]};
        }
    }   
    return answer;
}

Thoughts

2020년 카카오 인턴십 3번 문제로, 당시 못풀었던 기억이 난다.
우선 문제의 접근 방법을 처음에 보석의 종류를 각각 매핑하여 전체 보석의 갯수와 맵을 구현한다.
투 포인터를 사용하면 쉽게 풀 수 있는데, 이 아이디어를 생각하는게 실전에서는 쉽지 않았나보다.
단순히 주어진 보석리스틀 순회하면 이중 loop를 써야 할 것 같다..?
O(n^2)으로는 효율성을 통과할 수 없을 것 같은 직감을 갖고 투포인터를 어떻게 쓸지 고민하기 시작했다.
우선 처음과 끝포인터를 start, end라 하고, end를 하나씩 이동하면서 보석을 하나씩 담는다.
하나씩 담다가 모든 종류의 보석을 담으면, 이를 candidate_vect에 (start, end) 꼴로 추가해준다.
모든 종류의 보석을 가지고 있는 상태를 체크하는 함수를 하나 구현하여, 모든 종류가 담긴 상태에서는 start포인터를 하나씩 이동하면서 중간에 더 짧은 구간을 찾아낼 수 있다.
이 반복을 end가 끝까지 이동할 때까지 한다.

어려웠던 부분은 효율성을 만족시키기 위해 어떤 구조로 보석이 현재 모두 담겨있는지 체크하는 candidate_check 함수를 구현하는 것이었다.
백터로 보석을 담은 구조를 만들어 매번 순회하는 구조로 처음에 만들었더니 효율성이 도저히 통과되지 못해, map구조로 만들어 map의 전체 크기가 보석의 종류와 같은지 체크하는 방법을 했더니 통과되었다.

정리하자면,
투포인터를 사용해서 주어진 리스트를 탐색할 생각을 했는가,
이게 가장 핵심인 것 같다.

profile
Keep Learning👨🏻‍💻

0개의 댓글

관련 채용 정보