👀 문제 사이트 : https://www.acmicpc.net/problem/1786
문제 : 두 개의 문자열 t, p가 주어질 때 p가 t안에 몇 번 등장하고, 몇 번째 위치에서 등장하는지 출력한다.
자주 나오는 문자열 패턴매칭 문제로써 다양한 풀이방법이 존재한다.
구현하기 까다로운 KMP algorithm이나 유용성이 좋은 suffix array 등도 존재하지만, 문제 풀이로 z algorithm을 사용하였다.
z algorithm에서 z array는 문자열 s에서 시작하는 substring 중 prefix이면서 가장 긴 substring의 길이를 저장한다.
z array는 O(n) 시간안에 구현할 수 있다.
예)
ABCABCABAB
[0, 0, 0, 5, 0, 0, 2, 0, 2, 0]
start index x와 end index y를 0으로 초기화해준다. (이후에 패턴매칭이 되는 곳이 있으면 그곳에 시작 index와 끝 index로 저장될 예정)
구현 방법은 3가지 case로 나누어 생각해볼 수 있다.
반복문을 돌면서 값을 채워줄 것이고, 반복문에서 해당 index를 k로 둔다.
1) k > y 일 경우
2) k <= y && z[k-x] < y-k+1 일 경우
3) k <= y && z[k-x] >= y-k+1 일 경우
z array를 이용하여 이 문제처럼 pattern matching에 관련된 문제를 푸는 방법은 아래와 같다.
예)
p = AB
t = ABCDABCDABDD일 때,
s = p + "#" + t 로 둔다. -> AB#ABCDABCDABDD
z = [0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0]
-> p의 길이 : 2와 같은 값을 가지는 구간이 pattern matching이 되는 곳의 시작지점이다.
p와 t사이에 있는 값은 꼭 '#'이 아니어도 되며 문자열에 등장하지 않는 특수문자면 상관없다.
이렇게 문자열과 문자열 사이에 사용하지 않는 특수문자를 두고 합친 다음 z array를 만든 후에 앞쪽 문자열의 길이 값을 가지는 곳을 찾으면 된다.
def getZarr(string, z):
n = len(string)
x, y, i = 0, 0, 0
for k in range(1, n):
if k > y:
x, y = k, k
while y < n and string[y-x] == string[y]:
y += 1
z[k] = y-x
y -= 1
else:
i = k - x
if z[i] < y-k+1:
z[k] = z[i]
else:
x = k
while y < n and string[y-x] == string[y]:
y += 1
z[k] = y-x
y -= 1
return z
t = str(input())
p = str(input())
z = getZarr(p+"#"+t, [0] * (len(t)+len(p)+1))
results = []
count = 0
for i in range(len(p)+1, len(p)+len(t)+1):
if z[i] == len(p):
count += 1
results.append(i - len(p))
print(count)
for i in range(len(results)):
if i == len(results)-1:
print(results[i])
else:
print(results[i], end=" ")