Skip to content

Commit

Permalink
add BOJ :: 1509
Browse files Browse the repository at this point in the history
  • Loading branch information
now-water committed Jun 24, 2021
1 parent 6278d23 commit a57427f
Showing 1 changed file with 191 additions and 0 deletions.
191 changes: 191 additions & 0 deletions content/ps/BOJ/1509.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
---
title: '1509. 팰린드롬 분할'
metaTitle: '만렙 개발자 키우기'
metaDescription: '알고리즘 문제를 풀고 정리한 곳입니다.'
tags: ['DP']
date: '2021-06-24'
---

# 문제
- [1509. 팰린드롬 분할](https://www.acmicpc.net/problem/1509)

## 코드

<details><summary> 코드 보기 </summary>

``` java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;

public class Q1509 {
static String line;
static int size, dp[];
static boolean palin[][];
public static void main(String[] args) throws IOException {
init();
int ans = solution(0);
System.out.println(ans);
}

private static void init() throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
line = br. readLine();
size = line.length();
dp = new int[size];
palin = new boolean[size][size];
Arrays.fill(dp, -1);
}

private static boolean makePalindrom(int l, int r) {
if(l >= r || palin[l][r]) return palin[l][r] = true;
if(line.charAt(l) != line.charAt(r))
return palin[l][r] = false;
return palin[l][r] = makePalindrom(l+1, r-1);
}

private static int solution(int start) {
if(start == size) return 0;
if(dp[start] != -1) return dp[start];
int ret = dp[start] = 987654321;
for (int i = start; i < size; i++) {
if (makePalindrom(start, i)) {
ret = Math.min(ret, 1 + solution(i+1));
}
}
return dp[start] = ret;
}
}

```
</details>

## ⭐️느낀점⭐️
> 문자열 알고리즘을 사용하는 문제인가 해서 쫄았다가 DP 라는 유형을 보고 나서 접근하였다.
>
> DP 로 풀어야 한다는 힌트를 얻고도 쉽지 않은 문제였다. 다시 풀어볼 문제에 추가해야겠다.

## 풀이 📣
<hr/>

1️⃣ 입력받은 문자열의 부분 문자열들이 팰린드롬인지 상태정보를 저장하기 위한 DP를 사용한다.

- boolean palin[부분 문자열 시작 인덱스][부분 문자열 종료 인덱스]

- 재귀적으로 [시작인덱스+1][종료인덱스-1] 의 상태 정보를 채워나간다.

- 이미 구한 부분 문자열의 상태는 재계산하지 않는다.


2️⃣ 시작 인덱스를 옮겨가며 부분 문자열을 만든다.

- 시작은 0부터 한다.

- 재귀 중 특정 깊이에서, 시작 인덱스부터 오른쪽으로 한 칸씩 옮겨가는 포인터(i)를 하나 설정해준다.

- (start, i) 의 부분 문자열이 팰린드롬이라면, 즉 makePalindrom(start, i) 가 true이면 개수의 + 1을 해준다.

- 그리고 i + 1 부터는 새로운 재귀 함수를 시행해준다. -> ret = Math.min(ret, 1 + solution(i+1));

- 이렇게 시작 인덱스로만 모든 부분 문자열을 구할 수 있는 이유는 i를 start부터 시작해서 한 칸씩 옆으로 옮기기 때문이다.

- 그러면 i를 통해 start 혹은 이후의 모든 부분 문자열을 한 번 미리 계산할 수 있다.

- 따라서 start 자체를 한 칸씩 옮겼을 때 만들어지는 부분 문자열에서, 이전에 저장해둔 최소 분할 개수를 재사용할 수 있도록 해준다.


- 글로 설명하면 장황하고 이해하기 어려우니 예시를 들어보겠다.

- ex) abcdee 가 입력된다면, 재귀의 깊이를 따라 진행하였을 때, 처음엔 분할의 개수가 {a, b, c, d, e, e} 로 총 6개가 계산된다.

이후 start가 4일 때, i 가 5로 이동하면 부분 문자열은 ee 가 된다. 그럼 isPalindrom(start, i) 는 true가 되서, 1 + solution(i+1) = 1 + solution(6) = 1 + 0 = 1 이 된다.

이렇게 모든 경우를 커버하면서, 팰린드롬을 만났을 때 최소값으로 갱신되는 구조를 갖는다.


3️⃣ 최소 분할 개수를 출력하고 종료한다.

- solution(0) 을 출력한다.


## 실수 😅
<hr/>

- 처음에 팰린드롬을 판별하기 위해 부분 문자열을 만들고 그때마다 양쪽 끝에서부터 확인을 하였다.

- 이 부분의 불필요한 중복 계산을 막기 위해 재귀를 통한 메모이제이션을 구현하였다.


- 이후 분할 개수를 찾기 위해서 추가적인 인덱스 포인터(i)를 설정해서 옮겨가며 부분 문자열이 팰린드롬이 되는지 찾도록 구현하였다.


- 만약 팰린드롬이 되면 1 + solution(i+1, right) 를 최소 값으로 갱신하면서 새로운 재귀를 수행하였다.


- 팰린드롬이 아니라면 solution(left, i) + solution(i+1, right) 를 최소 값으로 갱신하면서 새로운 재귀를 수행하였다.


- 하지만 이 부분에서 solution(left + i) 를 앞쪽에서 구하였을 때, 상태값이 저장은 되지만 해당 상태값이 이후에 결코 재사용될 수 없었다.. => 정답은 나오지만, `시간 초과`


- 따라서 문자열에서 왼쪽의 부분 문자열의 상태가 아닌, 오른쪽의 부분 문자열의 상태를 메모이제이션 하도록 구현하여 통과할 수 있었다. => `시작 인덱스` 만 사용

<br/>

<details> <summary> 실패한 코드 </summary>

```java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;

public class Main {
static String line;
static int size, dp[][];
static boolean palin[][];
public static void main(String[] args) throws IOException {
init();
int ans = solution(0, size-1);
System.out.println(ans);
}

private static void init() throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
line = br. readLine();
size = line.length();
dp = new int[size][size];
palin = new boolean[size][size];
for (int i = 0; i < size; i++) {
Arrays.fill(dp[i], -1);
}
}

private static boolean makePalindrom(int l, int r) {
if(l >= r || palin[l][r]) return palin[l][r] = true;
if(line.charAt(l) != line.charAt(r))
return palin[l][r] = false;
return palin[l][r] = makePalindrom(l+1, r-1);
}

private static int solution(int left, int right) {
if(left == right) return 1;
if(left > right) return 0;
if(dp[left][right] != -1) return dp[left][right];
int ret = dp[left][right] = 987654321;
for (int i = left; i <= right; i++) {
if (makePalindrom(left, i)) {
ret = Math.min(ret, 1 + solution(i+1, right));
} else { // 이 부분에서 메모이제이션이 제대로 이루어지지 않음.
ret = Math.min(ret, solution(left, i) + solution(i+1, right));
}
}
return dp[left][right] = ret;
}
}

```

</details>

0 comments on commit a57427f

Please sign in to comment.