Heeveloper

와일드카드


1. Problem

screenshot


2. My Approach

  • 함수 judgewild(wild_idx, card_idx)는 와일드카드(wildcard)와 파일명(card)를 입력받으면 인덱스 0부터 비교를 시작한다.
  • wildcard[w]가 단순 문자일 경우 card[c]와 비교하여 맞으면 다음 인덱스로 넘어가 judgewild를 재귀호출한다.
  • wildcard[w]가 ‘?’인 경우 card[c]와 맞다고 하고 다음 인덱스로 넘어간다.
  • wildcard[w]가 ‘*‘인 경우 변수 skip과 함께 무한 루프로 진입한다.
    • 변수 skip은 해당 ‘*‘에 card의 몇개의 문자가 대응되는 지를 의미한다.
    • ‘*‘에는 0개의 문자부터 대응될 수 있으므로 skip은 0부터 시작한다.
    • skip은 0부터(대응하는 문자 없음) card.size()까지(나머지 문자 모두 대응)까지 증가하며 각각의 경우에 대응되었다고 가정하고 judgewild가 재귀호출된다.
    • while문 안에서 한 가지 skip의 judgewild라도 기저사례까지 도달하여 true를 반환하면 해당 skip만큼 ‘*‘에 대응한 경우가 성립하는 것이므로 최종적으로 true를 반환한다.

3. Implementation

bool judgeWild(int wild_idx, int card_idx){
    //base case
    if(wild_idx==wildcard.size() && card_idx == card.size())
        return true;

    // Just a word or '?'
    else if(wildcard[wild_idx] == card[card_idx] || (card_idx<card.size() && wildcard[wild_idx] == '?'))
        return judgeWild(wild_idx+1, card_idx+1);

    //  Case of '*'
    else if(wildcard[wild_idx] == '*'){
        int i=0;
        while(true) {
            // Test all the case
            if(judgeWild(wild_idx+1, card_idx+i)) return true;
            if(card_idx+i == card.size()) break;
            i++;
        }
    }
    return false;
}
int main( ) {
    int T;
    cin >> T;

    while(T--){
        list<string> ret;
        int num;
        cin >> wildcard;
        cin >> num;
        while(num--){
            cin >> card;
            if(judgeWild(0, 0))
                ret.push_back(card);
        }
        ret.sort();
        list<string>::iterator it = ret.begin();
        while(it != ret.end())
            cout<<*(it++)<<endl;
    }
    return 0;
}

  • wildcard에 대응되는 card들을 어순에 맞게 정렬하여 출력해야 하는데, 해당 card를 string타입의 list에 넣고 list.sort() 하여 간단하게 구현하였다.

4. Textbook Approach

  • 책 역시 ‘*’ 에 대응하는 문자의 수를 0개부터 card의 남은 문자열수만큼 하나씩 늘려주면서 체크를 해준다.
  • 다만 하나의 조건문 안에 재귀함수를 호출하면서 좀 더 재귀적인 구조의 알고리즘을 구현하였다.
  • memoization을 적용하여 중복되는 경우를 줄여주었다.

5. Implementation

string w, s;
int cache[101][101];
bool check(int w_idx, int s_idx) {

    int& ret = cache[w_idx][s_idx];
    if(ret != -1) return ret;

    while(w_idx < w.length() && s_idx < s.length() && (w[w_idx] == '?' || w[w_idx] == s[s_idx]))
        return ret = check(w_idx + 1, s_idx + 1);

    if(w_idx == w.length())
        return ret = (s_idx == s.length());

    if(w[w_idx] == '*')
        if(check(w_idx + 1, s_idx) || (s_idx < s.length() && check(w_idx, s_idx + 1)))
            return ret = 1;

    return ret = 0;
}

6. Analysis

  • 채점서버로 돌려본 결과 본인의 코드는 8ms, 책의 코드는 4ms의 수행시간이 걸렸다.
  • 본인의 코드는 while문 안에서 최악의 경우 N번의 재귀호출이 발생하고, 책의 코드 역시 N번의 재귀호출이 일어나 이 부분에 있어선 큰 차이가 없을 것으로 보인다. * 다만 책의 코드에선 memoization을 사용함으로써 중복되는 경우를 줄여주었으니 아마 이 점에서 수행시간이 줄었다고 볼 수 있다.

7. Feedback

  • 본 문제는 분할 정복 구조를 사용하여 복잡해 보이는 문제를 쉽게 해결가능한 하부 문제들로 쪼개어 해결한다.
  • 문제가 주어지면 가장 먼저 분할정복의 관점에서 바라봐야 할 필요성을 느낀다.
  • Memoization은 채점 기준에 따라 적용을 안해도 되지만, 보통 철저한 대회의 경우 Memoization을 이용하여 중복처리를 해주지 않으면 시간초과를 발생하게 한다. 특별한 상황을 제외하고 Memoization을 처리해주는 습관을 기르자.

Comments

Content
-->