Heeveloper

원주율 외우기


1. Problem

screenshot


2. My Approach

  • 주어진 문제로부터 따로 특별한 접근방법을 생각할 수 없어 모든 경우의 수를 비교하는 동적계획법을 적용하기로 했다.
  • 숫자는 오직 3개, 4개, 5개 단위로만 판단할 수 있다.
  • 주어진 숫자 배열들을 3개, 4개, 5개로 나눴을 때 각각의 레벨과 재귀적으로 구한 그 이후의 값들을 더해주어 비교한다.
    • getLevel() : 주어진 범위(3~4)에서 레벨을 측정한다.
    • Memoization() : getLevel을 재귀적으로 호출하여 비교한다.
  • getLevel() 함수의 구현은 간단해보이지만 예외처리를 철저하게 해줘야하기 때문에 의외로 애를 많이 먹었다. 처음에 짠 코드는 string에서 특정 인덱스의 값에 접근하고, character형 변수를 int형 변수로 변환해주는 둥 연산외에도 많은 처리를 하고 있는데 심지어 정답도 뜨지 않았다. 이렇게 짜면 안된다는 것을 인지하기 위해 올려둔다.

3. Implementation

  • 처음 getLevel( ) 코드
    int getLevel(string str){
      int grade = atoi(str.substr(1,1).c_str()) - atoi(str.substr(0,1).c_str());
      int second_grade = atoi(str.substr(2,1).c_str()) - atoi(str.substr(1,1).c_str());
      //  same numbers
      if(grade == 0 && grade == second_grade){
          for(int i=2;i<str.size();i++)
              if(grade != atoi(str.substr(i,1).c_str()) - atoi(str.substr(i-1,1).c_str())) return 10;
          return 1;
      }
      //  mono increment or mono decrement
      else if((grade == 1 || grade == -1) && grade == second_grade){
          for(int i=2;i<str.size();i++)
              if(grade != atoi(str.substr(i,1).c_str()) - atoi(str.substr(i-1,1).c_str())) return 10;
          return 2;
      }
      //  sequence
      else if(grade == second_grade){
          for(int i=2;i<str.size();i++)
              if(grade != atoi(str.substr(i,1).c_str()) - atoi(str.substr(i-1,1).c_str())) return 10;
          return 5;
      }
      else if(grade == -1*second_grade){
          for(int i=2;i<str.size();i++)
              if(str.substr(i,1) != str.substr(i-2,1)) return 10;
          return 4;
      }
      return 10;
    }
    
  • 책을 참고하여 수정된 getLever( ) 코드
    int getLevel(string str){
      if(str == string(str.size(),str[0])) return 1;
    
      bool progressvie = true;
    
      for(int i = 0; i < str.size()-1;i++)
          if(str[i+1]-str[i] != str[1]-str[0])
              progressvie = false;
    
      //  Mono increment or decrement
      if(progressvie && abs(str[1]-str[0]) == 1)
          return 2;
      //  Sequence
      else if(progressvie) return 5;
    
      // Repetition
      bool repetition = true;
      for(int i=0;i<str.size();i++)
          if(str[i] != str[i%2])
              repetition = false;
      if(repetition) return 4;
    
      return 10;
    }
    
    • 범위가 넓은 부분부터 확인하며 점근적으로 접근하는 것을 볼 수 있다.
    • 이와 같이 여러 예외경우를 조사해야 하는 경우 위와 같은 접근 방법을 알아두자.
  • Memoization( ) 코드
    int Memoization(int start){
      //base case
      if(start == input.size()) return 0;
      else if(start > input.size()-3) return 9999;
    
      int& ret = cache[start];
      if(ret != -1)
          return ret;
    
      ret = 999999;
      int q3 = getLevel(input.substr(start,3)) + Memoization(start+3);
      int q4 = getLevel(input.substr(start,4)) + Memoization(start+4);
      int q5 = getLevel(input.substr(start,5)) + Memoization(start+5);
    
      return ret = min(min(q3,q4),q5);
    }
    

    *


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