문제 링크
https://www.acmicpc.net/problem/1509
💡 풀이
기존 팰린드롬 문제와 다이나믹 프로그래밍을 함께 사용하는 문제입니다.
로직은 다음과 같습니다.
- 문자열의 i번째까지 탐색했을 때의 분할 개수의 최솟값을 `dp[i]`로 나타내겠습니다. 이 `dp[]` 배열 활용해 다이나믹 프로그래밍을 진행합니다.
- 맨 오른쪽 즉, 문자열의 i번째 인덱스를 기준으로 1 ~ (i - 1)번째까지 순회하며 팰린드롬을 확인합니다.
- 팰린드롬 확인은 두 포인터(Two Pointer)를 사용하여 확인했습니다.
- 만약 문자열의 j ~ i 까지의 문자열이 팰린드롬이라면, 기존에 계산했던 값과 새로이 계산된 값 중 최소값을 반환합니다. 이때, j ~ i 문자열이 팰린드롬일 경우, 분할된 개수는 `dp[j - 1] + 1`과 같습니다. `dp[j - 1]`은 j 번째 바로 이전까지의 분할 개수의 최솟값이고 `1`은 새로 만들어진 팰린드롬의 개수입니다.
- 위 과정을 문자열의 길이만큼 반복합니다.
팰린드롬 확인
우선, 두 포인터를 사용하여 인덱스 L ~ R의 문자열이 팰린드롬인지 확인하는 메서드를 작성합니다.
static boolean isPalindrome(int L, int R) {
L--; R--; // dp 배열의 인덱스가 문자열 인덱스보다 1 크기 때문에
while (L <= R) {
if (str.charAt(L) != str.charAt(R)) return false;
L++;
R--;
}
return true;
}
다이나믹 프로그래밍을 위한 `dp[]` 배열을 편의를 위해 1번 인덱스부터 시작하도록 설정했기 때문에, 넘어오는 두 가지의 포인터의 인덱스를 1씩 감소시켜 주어야 해당 문자열 인덱스에 올바르게 접근할 수 있습니다.
DP를 이용한 분할 개수의 최솟값
이제 1차원 `dp[]` 배열을 이용해 문자열의 길이만큼 탐색하며 최솟값을 찾아봅시다.
dp[1] = 1; // 1번 째는 팰린드롬 최대 1개
for (int i = 2; i < str.length() + 1; i++) {
for (int j = 1; j < i; j++) {
// j~i가 팰린드롬인지 확인
if (isPalindrome(j, i)) {
// 아직 체크한 이력이 없을 경우, 맨 뒤에 분할의 개수 추가
if (dp[i] == 0) {
dp[i] = dp[i - 1] + 1;
}
// 기존 계산값과 새로운 계산값 비교
dp[i] = Math.min(dp[i], dp[j - 1] + 1);
}
}
// 팰린드롬이 만들어지지 않았을 경우, 1개 추가
if (dp[i] == 0) {
dp[i] = dp[i - 1] + 1;
}
}
`dp[1]` 즉, 문자열의 맨 첫 글자만 있는 경우는 팰린드롬이 1개만 존재하기 때문에, 1로 고정시켜 줍니다. 그 후, 2번 인덱스부터 탐색을 시작합니다.
여기서 i는 두 포인터의 R(맨 오른쪽), j는 두 포인터의 L(맨 왼쪽)을 의미합니다. 따라서, i를 고정해 놓은 채로 j를 i - 1까지 순회하며 `dp[]` 배열을 갱신해 줍니다.
만약, i ~ j 문자열이 팰린드롬일 경우, 기존에 갱신했던 `dp[i]`와 현재 계산된 분할 개수인 `dp[j - 1] + 1` 중 작은 값으로 `dp[i]`를 갱신합니다. 이때, `dp[i] == 0`인 경우는 한 번도 갱신된 적이 없는 상태이기 때문에, 나올 수 있는 최대값(분할 개수를 1개 추가하는 것)을 넣어줍니다.
만약 j를 모두 순회했음에도 팰린드롬이 나오지 않았다면 `dp[i]`가 0 값을 가지고 있기 때문에, 분할 개수를 1개 추가하면 됩니다.
위 과정을 통해 전체 문자열의 팰린드롬 분할 개수의 최솟값을 구할 수 있습니다.
전체 코드
전체적인 코드는 다음과 같습니다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
class Q1509 {
static String str;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
str = br.readLine();
int[] dp = new int[str.length() + 1]; // i 번 째까지의 분할 개수의 최소값
dp[1] = 1; // 1번 째는 팰린드롬 최대 1개
for (int i = 2; i < str.length() + 1; i++) {
for (int j = 1; j < i; j++) {
// j~i가 팰린드롬인지 확인
if (isPalindrome(j, i)) {
// 아직 체크한 이력이 없을 경우, 맨 뒤에 분할의 개수 추가
if (dp[i] == 0) {
dp[i] = dp[i - 1] + 1;
}
// 기존 계산값과 새로운 계산값 비교
dp[i] = Math.min(dp[i], dp[j - 1] + 1);
}
}
// 팰린드롬이 만들어지지 않았을 경우, 1개 추가
if (dp[i] == 0) {
dp[i] = dp[i - 1] + 1;
}
}
System.out.println(dp[str.length()]);
}
static boolean isPalindrome(int L, int R) {
L--; R--; // dp 배열의 인덱스가 문자열 인덱스보다 1 크기 때문에
while (L <= R) {
if (str.charAt(L) != str.charAt(R)) return false;
L++;
R--;
}
return true;
}
}
'알고리즘 > BOJ' 카테고리의 다른 글
[BOJ] 17387 : 선분 교차 2(Java) (4) | 2024.02.27 |
---|---|
[BOJ] 14003 : 가장 긴 증가하는 부분 수열 5(Java) (1) | 2024.02.08 |
[BOJ] 4386 : 별자리 만들기(Java) (0) | 2024.02.06 |
[BOJ] 20303 : 할로윈의 양아치(Java) (1) | 2024.02.05 |
[BOJ] 6603 : 로또(Java) (0) | 2024.01.31 |