[1. 문제 재정의 및 추상화]
- 와일드카드 패턴을 앞에서 한 글자씩 파일명과 비교해서 모든 글자 일치 시, 와일드카드 패턴이 파일명과 대응된다 말함
- '?' 는 어떤 글자와도 대응된다 가정함
- '*' 는 0글자 이상의 어떤 문자열에도 대응 됨.
- 주어진 와일드카드 패턴에 대응되는 문자열 출력
- ex)
와일드카드 패턴 he?p 에 매치되는 문자열은 아래와 같다
heap, help
helpp 는 ? 가 lp 에 대응 될 수 없기 때문이다 - 패턴이 아닌 문자열은 알파벳 대소문자와 숫자만으로 구성 됨.
[2. 해결 계획]
- 완전 탐색 풀이
#include <string> // w: 와일드카드 패턴 // s: 문자열 bool match(const std::string &w, const std::string &s) { int pos = 0; while (pos < w.size() && pos < s.size() && (w[pos] == '?' || w[pos] == s[pos])) { // w 와 s 모두 확인 가능하고 // 현재 패턴이 '?' 인 경우 어떤 글자와도 대응되며 // 실제로 두 문자열에서 해당 위치의 문자는 같을 수 있다 // 이 경우 계속 확인을 진행 함 pos++; } // 위 반복문을 탈출하는 경우는 // 1. 패턴이 끝남 // 2. 문자열이 끝남 // 3. 패턴 내 문자가 '*' 임 => 패턴과 문자는 다를 수 밖에 없음 // 4. 해당 위치에서 패턴과 문자가 다름 // 패턴이 끝나는 경우 if (pos == w.size()) { // 문자열도 끝나야 // 문자열이 와일드 카드 패턴에 매칭 됨. return pos == s.size(); } if (w[pos] == '*') { // 이후 남은 문자열에 대해서 검증을 진행 // 현재 패턴이 '*' 인 경우는 그 어떤 문자에도 매칭 되므로 // '*' 에 몇 글자를 대응해야 할지 재귀 호출하면서 확인한다. for (int skip = 0; pos + skip <= s.size(); skip++) { if (match(w.substr(pos+1), s.substr(pos+skip))) { return true; } } } // 그 외 경우는 모두 매칭되지 않는다. return false; }
- 재귀 호출 시 w 와 s 는 전체 문자열 W 와 전체 패턴 S 에 대해서 접미사(suffix) 가 된다.
- 재귀가 발생하는 곳에서는 앞의 부분 문제가 더 이상 결과 값 계산에 영향을 주지 않는다.
pos 앞쪽이 매칭 되었으면, 벌써 true 를 반환 했을 것이기 때문에 - 따라서 최적 부분 구조를 만족 한다 볼 수 있다.(?)
- 패턴과 문자열의 검사의 부분 문제가 발생하는 시점을 캐시의 key 로 사용하여 memoization 을 적용 할 수 있다.
[3. 계획 검증]
[4. 구현]
#include <iostream>
#include <string>
// w 와 s 의 위치에서
// 패턴과 문자열에 각 문자가 대응되는지 확인한다.
int cache[101][101];
std::string W, S;
int match(int w, int s)
{
int &ret = cache[w][s];
if (ret != -1) {
return ret;
}
while (w < W.size() && s < S.size() &&
(W[w] == '?' || W[w] == S[s]))
{
w++;
s++;
}
// 패턴에 끝에 도달함
if (w == W.size()) {
// 문자열도 끝에 도달한 경우 서로 대응되고
// 그렇지 않다면 서로 대응하지 않음.
ret = (s == S.size());
return ret;
}
// 첫번째 while 에서
// 패턴이 '*' 여서 끝난 경우
if (W[w] == '*')
{
// 문자열을 '*'에 어디까지 대응 시킬 수 있는지 확인한다.
// W[w+1:] 과 S[s:], S[s+1:], ...
// * 에는 어떤 문자도 대응하지 않을 수 있다.
for (int skip=0; s + skip < S.size(); skip++)
{
if (match(w+1, s+skip)) {
ret = 1;
return ret;
}
}
}
ret = 0;
return ret;
}
int main()
{
int T;
char buf[128];
scanf("%d", &T);
for (; T > 0; T--) {
scanf("%s", buf);
W = buf;
int N;
scanf("%d", &N);
for (; N > 0; N--)
{
memset(cache, -1, sizeof(cache));
scanf("%s", buf);
S = buf;
if (match(0, 0)) {
printf("%s\n", buf);
}
}
}
return 0;
}
[5. 구현 2]
위 구현에서 두개의 반복문에 대해 재귀 호출로 바꾸어 메모아이제이션을 적용하면,
시간 복잡도를 낮출 수 있다.
int match(int w, int s)
{
int &ret = cache[w][s];
if (ret != -1) {
return ret;
}
/*while (w < W.size() && s < S.size() &&
(W[w] == '?' || W[w] == S[s]))
{
w++;
s++;
}*/
if (w < W.size() && s < S.size() &&
(W[w] == '?' || W[w] == S[s]))
{
ret = match(w+1, s+1);
return ret;
}
// 패턴에 끝에 도달함
if (w == W.size()) {
// 문자열도 끝에 도달한 경우 서로 대응되고
// 그렇지 않다면 서로 대응하지 않음.
ret = (s == S.size());
return ret;
}
// 첫번째 while 에서
// 패턴이 '*' 여서 끝난 경우
if (W[w] == '*')
{
// 문자열을 '*'에 어디까지 대응 시킬 수 있는지 확인한다.
// W[w+1:] 과 S[s:], S[s+1:], ...
// * 에는 어떤 문자도 대응하지 않을 수 있다.
/*for (int skip=0; s + skip <= S.size(); skip++)
{
if (match(w+1, s+skip)) {
ret = 1;
return ret;
}
}*/
// '*' 에 대응되는 것이 없을 때
if (match(w+1, s) || (s < S.size() && match(w, s+1)))
{
ret = 1;
return ret;
}
}
ret = 0;
return ret;
}
'알고리즘 > Algospot' 카테고리의 다른 글
[동적 계획법] JLIS (0) | 2022.02.16 |
---|---|
동적 계획법 (0) | 2022.02.14 |
[분할 정복] FENCE (0) | 2022.02.14 |
[분할 정복] QUADTREE (0) | 2022.02.05 |
[완전 탐색] CLOCKSYNC (0) | 2022.02.05 |