Algorithm/종만북
WILDCARD (DP)
ChoiSW
2025. 2. 22. 18:13
https://www.algospot.com/judge/problem/submit/WILDCARD
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
// Match 함수의 while 종료 조건
// 1. pos == wildcard.size()
// 와일드카드가 끝에 도달했을 때, 파일명도 끝에 도달했으면 true /파일명은 끝에 도달 못했으면 false
// 2. pos == filename.size()
// 파일명의 끝에 도달했으면,
// 1번을 먼저 처리했기에 filename은 끝이고 wildcard는 끝이 아니기에 무조건 false라고 생각했다.
// 하지만, wildcard[pos]가 * 가 아닌 경우에만 false 이다.
//
// 3. wildcard[pos] != '?' && wildcard[pos] != filename[pos]
// ?가 아니고 둘이 다를 때,
// wildcard[pos] == '*'일 때를 확인해야한다.
//
//
//
bool Match(string wildcard, string filename)
{
int pos = 0;
// 와일드카드와 파일명의 끝이 아니고 ( ?거나 문자가 같으면 ) pos++
while (pos < wildcard.size() && pos < filename.size() &&
(wildcard[pos] == '?' || wildcard[pos] == filename[pos]))
pos++;
// while 종료 조건 확인
// 1. pos == wildcard.size()
if (pos == wildcard.size())
*return pos == filename.size();
else
{
// 2. pos == filename.size()
if (wildcard[pos] != '*' && pos == filename.size())
return false;
}
if (wildcard[pos] == '*')
{
for (int skip = 0; pos + skip <= filename.size(); skip++)
{
// wildcard 의 *다음에 대해서
// filename 의 pos부터 끝까지 획인
if (Match(wildcard.substr(pos + 1), filename.substr(pos + skip)))
return true;
}
}*
return false;
}
int main()
{
int C; cin >> C;
while (C-- > 0)
{
vector<string> matched;
string wildcard; cin >> wildcard;
int n; cin >> n;
while (n-- > 0)
{
string filename; cin >> filename;
if (Match(wildcard, filename))
matched.push_back(filename);
}
sort(matched.begin(), matched.end());
for (string w : matched)
cout << w << "\\n";
}
}
아래의 else 부분은 그 아래의 if(wildcard[pos] == ‘*’) 처리로 인해 걸러질 수 있기에 없어도 되는 부분이다.
bool Match(string wildcard, string filename)
{
int pos = 0;
while (pos < wildcard.size() && pos < filename.size() &&
(wildcard[pos] == '?' || wildcard[pos] == filename[pos]))
pos++;
if (pos == wildcard.size())
*return pos == filename.size();
else
{
if (wildcard[pos] != '*' && pos == filename.size())
return false;
}
if (wildcard[pos] == '*')
{
for (int skip = 0; pos + skip <= filename.size(); skip++)
{
// wildcard 의 *다음에 대해서
// filename 의 pos부터 끝까지 획인
if (Match(wildcard.substr(pos + 1), filename.substr(pos + skip)))
return true;
}
}*
return false;
}
위의 풀이에서는 중복되는 부분문제가 있다.
wildcard = "*a*b*c"이고 filename = "aaabbc"인 경우
Match(*a*b*c, aaabbc) 에서 wildcard의 맨앞에 *이 나왔으므로, skip = 0부터 쭉 다 확인한다.
Match(a*b*c, aaabbc), Match(a*b*c, aabbc), Match(a*b*c, abbc) , Match(a*b*c, bbc), … 경우를 확인한다.
- (abc, aaabbc) 맨앞 a은 while문에 의해 넘어가지고(pos++) 그 다음은 *이기에 skip = 0부터 다 확인한다.
- (b*c, aabbc)
- … 모든 경우를 확인한다.
- (b*c, abbc)
- … 모든 경우를 확인한다.
- (b*c, bbc)
- … 모든 경우를 확인한다.
- (b*c, bc)
- … 모든 경우를 확인한다.
- (b*c, c)
- … 모든 경우를 확인한다.
- (b*c, )
- (abc, aabbc) 맨앞 a은 while문에 의해 넘어가지고(pos++) 그 다음은 *이기에 skip = 0부터 쭉 다 확인한다.
- (b*c, abbc)
- … 모든 경우를 확인한다.
- (b*c, bbc) → 중복 발생!!
- … 모든 경우를 확인한다.
- (b*c, bc)
- …
- (b*c, c)
- …
- (abc, abbc) …
- (abc, bbc) …
…
* 는 filename의 모든 부분에 대해 부분집합을 만들 수 있다. -> 중복 발생
예를 들어, "***"는 "abc"에 대해 다음과 같이 모든 경우를 고려해야 한다.
"", "a", "b", "c", "ab", "ac", "bc", "abc" // 2^3 = 8개
- 즉, 길이가 N인 문자열에 대해 *가 N개 있으면, 모든 부분집합을 탐색하는 것과 동일
- 부분집합의 개수 = 2^N
- 따라서, 100개의 ``가 있으면 최악의 경우 2^100개의 상태를 고려해야 함
이는 101 x 101 = 10201 개의 캐시를 넘어가기에 중복되는 부분문제가 발생하게 된다!!
중복되는 부분 문제 → 메모이제이션 적용
#include <iostream>
#include <string>
#include <vector>
#include <algorithm> // fill, sort
#include <cstring> // memset
using namespace std;
// while 종료 조건
// 1. pos == wildcard.size()
// 와일드카드가 끝에 도달했을 때, 파일명도 끝에 도달했으면 true
// 파일명은 끝에 도달 못했으면 false
// 2. pos == filename.size()
// 파일명의 끝에 도달했으면,
// 1번을 먼저 처리했기에 filename은 끝이고 wildcard는 끝이 아니기에 무조건 false라고 생각했다.
// 하지만, wildcard[pos]가 * 가 아닌 경우에만 false 이다.
//
// 3. wildcard[pos] != '?' && wildcard[pos] != filename[pos]
// ?가 아니고 둘이 다를 때,
// wildcard[pos] == '*'일 때를 확인해야한다.
//
// -1아직계산안함 / 0 계산했고 Match실패 / 1 계산했고 Match 성공
int Cached[101][101];
string wildcard;
string filename;
bool Match(int wc, int fn)
{
int& ret = Cached[wc][fn];
if (ret != -1) return ret;
// 와일드카드와 파일명의 끝이 아니고 ( ?거나 문자가 같으면 ) pos++
while (wc < wildcard.size() && fn < filename.size() &&
(wildcard[wc] == '?' || wildcard[wc] == filename[fn]))
{
wc++;
fn++;
}
// 와일드카드 끝
if (wc == wildcard.size())
return ret = (fn == filename.size());
if (wildcard[wc] == '*')
{
for (; fn <= filename.size(); fn++)
{
// wildcard 의 *다음에 대해서
// filename 의 pos부터 끝까지 획인
if (Match(wc + 1, fn))
return ret = 1;
}
}
return ret = 0;
}
int main()
{
int C; cin >> C;
while (C-- > 0)
{
vector<string> matched;
cin >> wildcard;
int n; cin >> n;
while (n-- > 0)
{
cin >> filename;
// fill(&Cached[0][0], &Cached[100][101], -1);
memset(Cached, -1, sizeof(Cached));
if (Match(0, 0))
matched.push_back(filename);
}
sort(matched.begin(), matched.end());
for (string w : matched)
cout << w << "\\n";
}
}
fill(&Cached[0][0], &Cached[100][101], -1); 에서 [인덱스-1][인덱스]로 해줘야함!
현재 시간복잡도는 O(n^3)이다.
아래의 방법을 통해 O(n^2) 시간복잡도로 풀 수 있다.
1차원 반복문을 재귀함수로 바꿔준다!
- Match함수의 while문을 통해 wc++; fn++; 해주는 부분을 다음과 같이 수정한다.
// 와일드카드와 파일명의 끝이 아니고 ?거나 문자가 같으면
if (wc < wildcard.size() && fn < filename.size() &&
(wildcard[wc] == '?' || wildcard[wc] == filename[fn]))
{
// 다음 글자부터 확인한다.
return ret = Match(wc + 1, fn + 1);
}
- *에 몇글자가 대응되어야 할지 확인하는 반복문을 다음과 같이 수정한다.
if (wildcard[wc] == '*')
{
// *에 0글자 대응과 1글자 대응 모두 고려
if ( Match(wc + 1, fn) ||
fn < filename.size() && Match(wc, fn+1) )
return ret = 1;
}
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstring> // memset
using namespace std;
// -1아직계산안함 / 0 계산했고 Match실패 / 1 계산했고 Match 성공
int Cached[101][101];
string wildcard;
string filename;
bool Match(int wc, int fn)
{
int& ret = Cached[wc][fn];
if (ret != -1) return ret;
// 와일드카드와 파일명의 끝이 아니고 ?거나 문자가 같으면
if (wc < wildcard.size() && fn < filename.size() &&
(wildcard[wc] == '?' || wildcard[wc] == filename[fn]))
{
// 다음 글자부터 확인한다.
return ret = Match(wc + 1, fn + 1);
}
// 와일드카드 끝
if (wc == wildcard.size())
return ret = (fn == filename.size());
if (wildcard[wc] == '*')
{
// *에 0글자 대응과 1글자 대응 모두 고려
if ( Match(wc + 1, fn) ||
fn < filename.size() && Match(wc, fn+1) )
return ret = 1;
}
return ret = 0;
}
int main()
{
int C; cin >> C;
while (C-- > 0)
{
vector<string> matched;
cin >> wildcard;
int n; cin >> n;
while (n-- > 0)
{
cin >> filename;
//fill(&Cached[0][0], &Cached[100][101], -1);
memset(Cached, -1, sizeof(Cached));
if (Match(0, 0))
matched.push_back(filename);
}
sort(matched.begin(), matched.end());
for (string w : matched)
cout << w << "\\n";
}
}