문제
앞에서 뒤로 보나, 뒤에서 앞으로 보나 같은 수열을 팰린드롬이라고 한다. 예를 들어 {1}, {1, 2, 1}, {1, 2, 2, 1}과 같은 수열은 팰린드롬이지만, {1, 2, 3}, {1, 2, 3, 2} 등은 팰린드롬이 아니다.
한 수열이 주어졌을 때, 이 수열에 최소 개수의 수를 끼워 넣어 팰린드롬을 만들려고 한다. 최소 몇 개의 수를 끼워 넣으면 되는지를 알아내는 프로그램을 작성하시오.
입력
첫째 줄에 수열의 길이 N(1≤N≤5,000)이 주어진다. 다음 줄에는 N개의 수열을 이루는 수들이 주어진다. 각 수들은 int 범위이다.
출력
첫째 줄에 끼워 넣을 수들의 최소 개수를 출력한다.
🔎문제 해석
DP를 기반으로 해서 다른 알고리즘을 병행하면서 해결했다.
DP를 통해서 쓸데 없는 연산을 하지 않는 메모이제이션을 활용해서, 값을 미리 저장해 두고, 투 포인터를 통해서 범위를 좁혀나갔다.
펠린드롬은 앞에서 뒤로 보나, 뒤에서 앞으로 보나 같은 수열을 팰린드롬이라고 한다.
그럼 양끝지점이 같아야 한다는 뜻이다.
여기서 양끝지점이 다르다면 우리는 왼쪽에 끼워 넣을지, 오른쪽에 끼워넣을지 잘 생각해봐야 한다.
start = 1 / end = n으로 선언하겠습니다.
start와 end를 비교하면서, start와 end를 계속해서 조정해줬습니다.
문제에 테스트케이스를 예시로 들어서 설명하겠습니다.
![](https://blog.kakaocdn.net/dn/3QOFM/btsdOAhB92a/A4EJnrkfzsK5WuanUulTi0/img.png)
여기서 1과 2를 비교합니다.
1과 2는 다르기에, 1을 2 오른쪽에 끼우거나 2를 1 왼쪽에 끼워야 합니다.
항상 이렇게 끼우는 결과는 왼쪽의 결괏값이 적은지, 오른쪽의 결과값이 작은지 비교해줘야 합니다.
제 코드로는 오른쪽에 끼우는 작업을 우선으로 해줬기 때문에, 오른쪽에 끼우는 작업부터 진행하겠습니다.
빨간색 글자가 추가된 수열이고, 파란색 글자는 현재 제가 추가한 숫자의 개수입니다.
이렇게 되면 우리는 start와 end를 조정합니다.
start를 한 칸 오른쪽으로 움직이고, end는 그대로 둡니다.
start와 end의 범위는 같기 때문에, 각각 한 칸씩 옮겨줍니다.
두 값이 다르기 때문에, 오른쪽에 왼쪽 값을 끼워줍니다.
이렇게 되면 최종적인 팰린드롬이 완성됩니다.
해당 과정에서 추가된 숫자의 개수는 총 2개인 것을 알 수 있습니다.
start와 end 지점에서의 숫자가 다를 시, 왼쪽, 오른쪽의 숫자를 반대쪽에 끼워주면 됩니다.
DP [i][j]는 i~j까지 팰린드롬을 만들 때 필요한 최소의 추가 숫자를 의미합니다.
DP배열을 -1로 초기화하고, 한 번이라도 연산이 진행된 경우에는(-1이 아닌 경우)는 그대로 값을 반환해 줍니다.
💻전체 코드
#include <iostream>
#include <vector>
using namespace std;
int n;
vector<int> ary;
vector<vector<int> > dp;
int funct(int start, int end);
int main()
{
cin.tie(NULL);
ios_base ::sync_with_stdio(false);
cin >> n;
ary.resize(n + 1, 0);
dp.resize(n + 1, vector<int>(n + 1, -1));
for (int i = 1; i <= n; i++)
{
cin >> ary[i];
}
cout << funct(1, n) << "\n";
}
int funct(int start, int end)
{
if (start > end){ //범위 초과
return 0;
}
if (dp[start][end] != -1){ //한번 구한 dp 배열은 다시 구할 필요가 없슴
return dp[start][end];
}
if (ary[start] == ary[end]){ // 같으면 해줄 필요가 없는데.
dp[start][end] = funct(start + 1, end - 1);
return dp[start][end];
}
else
{ // 다를 경우. 끼워넣는 방법은 몇가지일까. 왼쪽을 오른쪽끝에 넣기 vs 오른쪽을 왼쪽 끝에 넣기?
int right = 1 + funct(start, end - 1); //오른쪽 숫자를 왼쪽에 끼워넣기.
int left = 1 + funct(start + 1, end); //왼쪽 숫자를 오른쪽에 끼워넣기.
if (left < right) //최소로 끼워야 함.
dp[start][end] = left;
else
dp[start][end] = right;
}
return dp[start][end];
}
😀느낀 점
- cin.tie(null)과 sync_with_stdio(false)를 잘 추가해주지 않는 편이었는데,
해당 코드가 시간초과가 뜨길래, 질문게시판을 참고해 보니 , 해당 문구를 추가하면 시간초과가 해결된다고 해서, 추가하니까 허망했다. - 재귀 말고, 점화식으로 했어야 했는데, 점화식이 막상 떠오르지가 않았다.
- DP가 약점인 만큼 많이 풀고 싶은데, 정말 어렵다.
'CodingTest > Baekjoon' 카테고리의 다른 글
[백준 17144] - 미세먼지 안녕!(C++)[골드4] (0) | 2023.05.11 |
---|---|
[백준 10942] - 팰린드롬?(C++)[골드4] (0) | 2023.05.04 |
[백준 1520] - 내리막길(C++)[골드3] (0) | 2023.05.03 |
[백준 1005] - ACM Craft(C++)[골드3] (0) | 2023.05.02 |
[백준 16933] - 벽 부수고 이동하기 3(C++)[골드1] (0) | 2023.04.09 |