길이 n의 문자열 S[0:n]에 대하여 ArrayZ[0:n]를 O(n)에 구한다.
i | Z | a | n | a | n | a | b |
0 | 6 | a | n | a | n | a | b |
1 | 0 | n | a | n | a | b | |
2 | 3 | a | n | a | b | ||
3 | 0 | n | a | b | |||
4 | 1 | a | b | ||||
5 | 0 | b |
Z[i]는 S[0:]와 S[i:]의 최대 공통 접두사의 길이이다.
코드는 다음과 같이 작성하였다.
def getZ(s):
l,r=0,0
ret=[0]*len(s)
ret[0]=len(s)
for i in range(1,len(s)):
if i>r:
l=r=i
while r<len(s) and s[r]==s[r-l]:
r+=1
ret[i]=r-l
r-=1
else:
if ret[i-l]<=r-i:
ret[i]=ret[i-l]
else:
l=i
while r<len(s) and s[r]==s[r-l]:
r+=1
ret[i]=r-l
r-=1
return ret
구간 [l:r]를 S[l:r]와 S[0:]의 접두사가 일치하면서 r이 최대인 구간으로 정의한다.
그러면 Z[i]는 각 i에서의 구간의 길이 r−l 와 같다.
Z[0]은 S의 길이와 같음이 자명하다. l과 r을 0으로 초기화 한다.
이제 i=1부터 i=|s|−1까지 다음과 같은 과정을 반복한다.
- Case1: i>r일 때
아무 정보가 없는 곳에 도달하였다.
l과 r을 현 위치로 이동하고, S[r]과 S[r−l]이 달라질 때 (구간 [l:r]의 정의를 위배) 까지 r을 오른쪽으로 이동한다.
이렇게 구한 l과 r의 차로 Z[i]의 값을 구할 수 있다.
- Case2: i≤r일 때
현재 가지고 있는 정보가 완전할 때와 그렇지 않을 때로 나눌 수 있다.
Z[i−l]≤r−i에서 가지고 있는 정보는 완전하다.
S[l:r]과 S[0:r−l]이 같으므로 Z[i]와 Z[i−l]은 같다.
Z[i−l]>r−i에서 가지고 있는 정보는 제한적이다. S[i:]와 S[0:]의 접두사가 r−i글자 이상 일치할 수 있다는 뜻이다.
l을 i로 이동하고 Case1과 같이 r을 이동시키면 Z[i]의 값을 구할 수 있다.
처음 표에서 쓴 문자열 ananab으로 자세히 알아보자.
Z[0]
문자열의 길이와 같으므로 6이다.
l과 r을 0으로 초기화 한다.
Z[1] (i=1,l=0,r=0)
i가 r보다 커 Case1에 해당한다.
S | a | n | a | n | a | b |
S[i:] | n | a | n | a | b | |
l,r | l, r = 1 |
l과 r을 1(=i) 로 초기화 한 다음, S[r]과 S[r−l]이 달라질 때 까지 r을 오른쪽으로 이동시킨다.
하지만 처음부터 S[1]과 S[1−1]이 각각 n과 a로 다르므로 l=1, r=1로 끝난다.
그 결과 Z[1]=0이다.
이렇게 바로 끝날 때 r이 l 뒤로 가는데, 그 다음 호출에선 다시 Case 1로 돌아와 l=r=i를 실행하므로 문제 없는 것 같다.
Z[2] (i=2,l=1,r=0)
i가 r보다 커 Case1에 해당한다.
S | a | n | a | n | a | b |
S[i:] | a | n | a | b | ||
l,r | l = 2 | r = 5 |
l과 r을 2(=i) 로 초기화 한 다음, S[r]과 S[r−l]이 달라질 때 까지 r을 오른쪽으로 이동시킨다.
r=5에서 S[5]와 S[5−2]이 각각 b와 n으로 다르므로 l=2, r=5로 끝난다.
그 결과 Z[2]=3이다.
Z[3] (i=3,l=2,r=4)
i가 r이하이고, Z[3−2]≤4−3이므로 Case2중 첫 째 경우에 해당한다.
S | a | n | a | n | a | b |
S[i:] | n | a | b | |||
l,r |
S[l:r]과 S[0:r−l]이 일치함을 확신할 수 있고, 실제로 ana (anana)로 같다.
따라서 Z[3]과 Z[1]도 같다고 확신할 수 있다.
Z[3]=Z[1]=0 이다.
Z[4] (i=4,l=2,r=4)
i가 r이하이고, Z[4−2]≤4−4가 성립하지 않아 Case2중 둘 째 경우에 해당한다.
S | a | n | a | n | a | b |
S[i:] | a | b | ||||
l,r | l = 4 | r = 5 |
l을 i(=4)로 이동하고, S[r]과 S[r−l]이 달라질 때 까지 r을 오른쪽으로 이동시킨다.
r=5에서 S[5], S[5−4]이 각각 b, n으로 달라지므로 l=4, r=5로 끝난다.
따라서 Z[4]=1이다.
i=5 에서도 Case1에서의 논리로 값을 구할 수 있고, 최종적으로 Z=[6,0,3,0,1,0] 을 얻을 수 있다.
공부한 곳
'알고리즘 > 필기' 카테고리의 다른 글
라빈-카프와 부분문자열 쿼리 (0) | 2022.07.22 |
---|---|
연속한 수의 XOR sum (0) | 2020.12.25 |
치킨 쿠폰 (0) | 2020.11.25 |
유니온 파인드 (0) | 2020.09.11 |
댓글