일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 코딩테스트
- FLAB
- Spring
- 성능테스트
- nGrinder
- 트러블슈팅
- MySQL
- EC2
- java
- 도커
- github
- error
- F-Lab
- 에프랩
- 플러터
- 데이터구조
- 부트캠프
- 후기
- 백준
- AWS
- 자바백엔드
- backend
- Flutter
- 백엔드
- 멘토링
- IntelliJ
- 레디스
- 알고리즘
- 자바
- grafana
- Today
- Total
민스씨의 일취일장
h.o.Algorithm | Java | 백준 16916 - 문자열 포함 판독 KMP 알고리즘 본문
h.o.Algorithm | Java | 백준 16916 - 문자열 포함 판독 KMP 알고리즘
읻민스 2025. 2. 20. 17:03백준 16916 문제를 해결하는 과정에서 사용한 KMP 알고리즘에 대한 기록이다.
h.o.Algorithm - Java 백준 16916
문제
두 개의 문자열이 주어진다. 두 번째 문자열이 첫 번째 문자열에 포함되는지 여부를 판단하는 문제이다.
풀이
String.contins()를 이용한 풀이
간단하게 String.contains() 메소드를 이용해서 문제를 해결을 시도하였다.
import java.io.*;
public class baekjoon16916 {
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String REF = br.readLine();
String tester = br.readLine();
if(REF.contains(tester)){
System.out.println(1);
} else {
System.out.println(0);
}
}
}
하지만 결과는 시간초과이다.
분석
contains()는 내부적으로 indexOf(), 즉 반복문을 사용해 문자열 검색을 수행한다. 문자열 하나당 패턴 문자열 전체를 탐색하기 때문에 문자열 N과 패턴 문자열 M이 주어질 경우 시간복잡도가 O(N*M)을 갖는다.
새로운 풀이
KMP (Knuth-Morris-Pratt) 알고리즘을 이용한 풀이
KMP 알고리즘은 패턴 문자열의 패턴 정보를 배열에 넣어두고, 미리 파악해 높은 패턴 정보를 활용해 문자열 포함을 확인하는 재미있는 알고리즘이다. 살짝 복잡할 수 있지만 패터닝과정과, 패터닝 이후 일치하는 패턴을 찾는 로직이 거의 비슷하기 때문에 한 번만 잘 이해하면 잘 사용할 수 있을 것이다.
패터닝
먼저 패터닝 알고리즘을 먼저 살펴보자. 패턴 텍스트만을 사용해 패턴 정보를 하나의 배열에 담는 과정이다.
private static int[] computePi(String pattern){
int m = pattern.length();
int[] pi = new int[m];
int j = 0;
for(int i=1; i<m; i++){
while(j>0 && pattern.charAt(i) != pattern.charAt(j)){
j = pi[j-1];
}
if(pattern.charAt(i) == pattern.charAt(j)){
pi[i] = ++j;
}
}
return pi;
}
패턴 길이의 배열을 하나 생성해 준다. j는 패턴을 추적할 인덱스이고, for 문의 i는 패턴 전체를 순서대로 추적할 인덱스 이다. 이 두 인덱스가 가리키는 문자가 같을 경우 배열에 j+1 값을 담아주고, 해당 문자부터 일치하는 문자까지 j 값을 늘려 가며 배열에 담아준다.
예) 문자열 3개가 일치하면 배열에 해당 i 번째 위치에서부터 1, 2, 3가 할당된다.
이렇게 추적해 가다가 일치하지 않는 문자가 나타나면 j 값을 배열의 j-1 번째 값으로 재설정 해준다. 이 때 j는 실제 일치한 패턴의 수이고, j-1은 배열은 0부터 센다는 걸 생각하면 기억하기 쉽다.
텍스트와 패턴 비교
이제 작성한 패턴 배열 정보를 이용해 텍스트와 패턴을 비교하는 과정을 살펴보자.
private static boolean kmp(String text, String pattern){
int[] pi = computePi(pattern);
int n = text.length();
int m = pattern.length();
int j =0;
for(int i=0; i<n; i++){
while(j>0 && text.charAt(i) != pattern.charAt(j)){
j = pi[j-1];
}
if(text.charAt(i) == pattern.charAt(j)){
if(j == m-1){
return true;
}
j++;
}
}
return false;
}
패터닝 과정과의 차이점은 같은 문자를 가리킬 때 현재 j값이 패턴 배열의 마지막 인덱스 값인지 확인하는 것이다. j값이 패턴 배열의 마지막 인덱스에 도달했다는 것은 패턴 문자열이 모두 텍스트에 포함되었다는 의미이다.
전체 코드
import java.io.*;
public class baekjoon16916 {
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String REF = br.readLine();
String tester = br.readLine();
if(kmp(REF, tester)){
System.out.println(1);
} else {
System.out.println(0);
}
}
private static boolean kmp(String text, String pattern){
int[] pi = computePi(pattern);
int n = text.length();
int m = pattern.length();
int j =0;
for(int i=0; i<n; i++){
while(j>0 && text.charAt(i) != pattern.charAt(j)){
j = pi[j-1];
}
if(text.charAt(i) == pattern.charAt(j)){
if(j == m-1){
return true;
}
j++;
}
}
return false;
}
private static int[] computePi(String pattern){
int m = pattern.length();
int[] pi = new int[m];
int j = 0;
for(int i=1; i<m; i++){
while(j>0 && pattern.charAt(i) != pattern.charAt(j)){
j = pi[j-1];
}
if(pattern.charAt(i) == pattern.charAt(j)){
pi[i] = ++j;
}
}
return pi;
}
}
해당 코드 정보는 아래 GitHub 링크에서도 확인할 수 있다.
Study/Java/DataStructureAlgorithm/src/알고리즘문제풀이/문자열/baekjoon16916.java at main · YdMinS/Study
Contribute to YdMinS/Study development by creating an account on GitHub.
github.com
분석
- 패터닝 시간복잡도
패터닝 과정은 길이가 M인 패턴 텍스트를 한 번 순회하기 때문에 O(M)이다.
- 텍스트와 패턴 비교과정 시간 복잡도
텍스트 비교 과정에서 길이가 N인 텍스트를 최대 한 번 순회하기 때문에 O(N)이다.
- 전체 시간복잡도
전체 시간복잡도는 O(N) + O(M) ~ O(N)이다. contains()의 O(N*M)~(M^2)와 비교할 때 상당한 차이가 있는 것을 볼 수 있다.
'Programming Language & Framework > JAVA & Spring' 카테고리의 다른 글
rTcl | Java Map은 순회가 안된다? (feat. Map.Entry) (0) | 2025.02.18 |
---|---|
rTcl | Java 빈줄 (EOF) 처리 - null vs. isEmpty() vs. isBlank() (2) | 2025.02.08 |
rTcl | Java | String indexOf()에 대해서 알아보기 (0) | 2024.12.24 |
TIssue | Spring Data JPA | 실전! 스프링 데이터 JPA | Class Projection 안되는 이슈 (0) | 2024.12.05 |
TIssue | JPA | 스프링 부트와 JPA 활용 2 - 김영한 | Fetch Join에 distinct 적용 안해도 중복 제거되는 상황 (0) | 2024.11.28 |