코딩테스트/백준

P.1509 팰린드롬 분할

박 성 하 2023. 9. 29. 23:02
728x90

코드

import java.io.*;
import java.util.Arrays;

public class Main {

    static String[] str;
    static int len;
    static boolean[][] isPalindrome;

    static int[] dp;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        str = br.readLine().split("");
        len = str.length;
        isPalindrome = new boolean[len + 1][len + 1];

        for (int i = 0; i < len; i++) {
            for(int j = 1; j + i <= len; j++) {
                if (i == 0) isPalindrome[j][j] = true;
                else if (checkPalindrome(j, j + i)) isPalindrome[j][j + i] = true;
                else isPalindrome[j][j + i] = false;
            }
        }

        dp = new int[len + 1];
        Arrays.fill(dp, len);
        dp[0] = 0;
        for (int i = 1; i <= len; i++) {
            for (int j = 1; j <= i; j++) {
                if (isPalindrome[j][i]) dp[i] = Math.min(dp[i], dp[j - 1] + 1);
            }
        }

        bw.write(Integer.toString(dp[len]));
        bw.flush();
    }

    private static boolean checkPalindrome(int start, int end) {
        if (str[start - 1].equals(str[end - 1])) {
            if (end - start == 1 || isPalindrome[start + 1][end - 1]) return true;
        }
        return false;
    }
}

코드 설명

이해를 잘 못해서 시간이 많이 걸린 문제였다.

이해가 가지 않은 부분은 두 가지였다.

 

1. 분할한 것이 모두 팰린드롬이어야하나?

2. 분할한 것의 개수를 세는 것인가 집합의 개수를 세는 것인가?

 

예시를 뜯어보느라 시간이 많이 걸렸는데 다음을 구하는 것이 정답이었다.

팰린드롬인 문자열을 가진 집합 중 부분문자열의 수가 가장 적은 것의 개수

 

이제 문제를 풀기 위해 두 가지를 구해야 한다.

1. 부분 팰린드롬을 모두 구한다.
2. 부분문자열의 수가 가장 적은 것을 구한다.

 

1. 부분 팰린드롬 구하기

for (int i = 0; i < len; i++) {
    for(int j = 1; j + i <= len; j++) {
        if (i == 0) isPalindrome[j][j] = true;
        else if (checkPalindrome(j, j + i)) isPalindrome[j][j + i] = true;
        else isPalindrome[j][j + i] = false;
    }
}
private static boolean checkPalindrome(int start, int end) {
    if (str[start - 1].equals(str[end - 1])) {
        if (end - start == 1 || isPalindrome[start + 1][end - 1]) return true;
    }
    return false;
}

1) 길이가 1일 경우, 팰린드롬이다.

2) 길이가 2일 경우, 두 글자가 같으면 팰린드롬이다.

3) 길이가 3이상일 경우, 첫 글자와 마지막 글자가 같고 (첫 글자 + 1) ~ (마지막 글자 - 1)이 팰린드롬이면 팰린드롬이다.

 

Bottom-Up 방식으로 풀어야하기 때문에 길이가 짧은 것부터 먼저 쌓아 올라가야한다.

 

ABACABA일 때, isPalindrome 배열은 다음과 같다.

1 0 1 0 0 0 1
0 1 0 0 0 1 0
0 0 1 0 1 0 0
0 0 0 1 0 0 0
0 0 0 0 1 0 1
0 0 0 0 0 1 0
0 0 0 0 0 0 1

파란색 1은 길이가 1인 부분문자열이다.

 

idx 1~3일 때 부분문자열은 ABA이며 양 끝 글자가 A로 같고 isPalindrome[1 + 1][3 - 1] == isPalindrome[2][2] == true이기 때문에 isPalindrome[1][3] = true이다.

 

idx 1 ~ 7일 때 부분문자열인 ABACABA가 팰린드롬인지 확인하는 과정은 다음과 같다.

1) 양 끝 글자가 A로 같고 idx 2~6인 부분문자열이 팰린드롬인지 확인한다.

2) idx가 2~6일 때, 양 끝 글자가 B로 같고 idx 3~5인 부분문자열이 팰린드롬인지 확인한다.

3) idx가 3~5일 때, 양 끝 글자가 A로 같고 idx 4~4인 부분문자열이 팰린드롬인지 확인한다.

4) idx가 4일 때, 길이가 1인 부분문자열이므로 팰린드롬이다.

 

코드 내에서는 Bottom-Up과정이기 때문에 4 -> 3 -> 2 -> 1순서로 확인된다.

 

2. 부분문자열의 수가 가장 적은 것 구하기

dp[n] = n까지의 문자열 중 부분문자열의 수가 가장 적은 것의 개수

일단 조건은 부분문자열에 팰린드롬 문자열이 포함되어야 한다.

 

만약 ABACABA에서 ABA까지 팰린드롬이므로 dp[3]이 1이라고 가정해보자.

dp[4]는 4번째까지의 문자열 중 부분문자열 개수의 최솟값을 구하게 되는데 str[4]는 한 글자이므로 팰린드롬이며, dp[3]까지의 최솟값은 1이므로 dp[4] = n{'ABA', 'C'} = 2이다.

 

즉, n번째 수가 팰린드롬이라면 n - 1번째까지 결과값에 + 1을 더한다.

하지만 n번째까지의 수를 보기 위해서는 (1, 2~n), (1~2, 3~n) ... (1~n - 1, n)처럼 다양한 방법이 있으므로 그 중에서 최솟값을 찾아야하므로 min함수를 이용한다.

dp[n] = min(dp[n], dp[n - 1] + 1)
dp = new int[len + 1];
Arrays.fill(dp, len);
dp[0] = 0;
for (int i = 1; i <= len; i++) {
    for (int j = 1; j <= i; j++) {
        if (isPalindrome[j][i]) dp[i] = Math.min(dp[i], dp[j - 1] + 1);
    }
}
728x90

'코딩테스트 > 백준' 카테고리의 다른 글

P.12850 본대 산책2  (0) 2023.10.01
P.1799 비숍  (0) 2023.09.30
P.16946 벽 부수고 이동하기 4  (0) 2023.09.28
P.10775 공항  (0) 2023.09.27
P.1766 문제집  (0) 2023.09.25