백준 - JAVA/동적 계획법 1
[백준] 11054번 가장 긴 바이토닉 부분 수열 _ JAVA ( 주석 설명 )
wch_s
2023. 3. 2. 17:45
풀이
1)
1 - dp1[i] : ary[0]~ary[i-1] 까지 탐색을 하며 ary[i]보다 작은 값의 갯수를 저장한다.
2 - dp2[i] : ary[i]~ary[N] 까지 탐색을 하며 ary[i]보다 작은 값의 갯수를 저장한다.
-> 증감을 파악하기 위해서
2)
'가장 긴 증가하는 부분 수열' 문제와 같이 처음 dp1[N], dp2[N] == 0 (빈 값) 일 때, 자기 자신도 갯수에 포함해야 하므로 1로 초기화 한다.
3)
dp1[N]을 구하는 함수에서는 1)-1 식을 코드로
dp2[N]을 구하는 함수에서는 2)-2 식을 코드로 작성을 한다.
ex> dp1
for(int i=0;i<N;++i){
if(ary[i] < ary[N]){
dp[N] = Math.max(dp[N],dp1_funct(i)+1);
}
}
4)
dp1[], dp2[] 배열에 각 인덱스에 있는 원소를 기준으로 증감을 담았기 때문에,
가장 긴 바이토닉 부분 수열은 dp1[], dp2[] 의 각 인덱스를 더한 값 중 가장 큰 값이다.
1번
→ Top-down 방식
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
static int[] ary; //입력받은 수열을 담을 공간이다.
static int[] dp1; //증가하는 부분수열을 담을 공간이다.
static int[] dp2; //감소하는 부분수열을 담을 공간이다.
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
int N = Integer.parseInt(br.readLine());
ary = new int[N];
dp1 = new int[N];
dp2 = new int[N];
//수열 A를 입력받는다.
st = new StringTokenizer(br.readLine());
for(int i=0;i<N;++i){
ary[i] = Integer.parseInt(st.nextToken());
}
//dp1[] 구하기
for(int i=0;i<N;++i) {
increase_length(i);
}
//dp2[] 구하기
for(int i=0;i<N;++i) {
decrease_length(i);
}
//증가 추세와 감소 추세를 담은 dp1, dp2 배열의 합이 바이토닉 부분 수열이다.
//이 중 가장 긴 바이토닉 부분 수열을 찾는다.
//본인 자신이 2번 카운팅 되기 때문에 -1을 해준다.
int in_de_length = 0;
for(int i=0;i<N;++i){
in_de_length = Math.max(in_de_length, dp1[i]+dp2[i]-1);
}
System.out.println(in_de_length);
}
//N 이전의 증가 배열의 부분수열 중 가장 큰 길이의 값
private static int increase_length(int N) {
if(dp1[N]==0) {
dp1[N] = 1;
//본인보다 작은 인덱스에서 작은 값의 갯수를 찾는다. => 0~N까지 증가 추세
for(int i=0;i<N;++i){
if(ary[N] > ary[i]) {
dp1[N] = Math.max(dp1[N], increase_length(i) + 1);
}
}
}
return dp1[N];
}
//N 이후의 감소 배열의 부분수열 중 가장 큰 길이의 값
private static int decrease_length(int N) {
if(dp2[N]==0) {
dp2[N] = 1;
//본인보다 큰 인덱스에서 작은 값의 갯수를 찾는다. => N~ary.length 까지 감소 추세
for(int i=N;i< ary.length;++i) {
if (ary[N] > ary[i]) {
dp2[N] = Math.max(dp2[N], decrease_length(i) + 1);
}
}
}
return dp2[N];
}
}
2번
→ Bottom-up 방식
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
static int[] ary; //입력받은 수열을 담을 공간이다.
static int[] dp1; //증가하는 부분수열을 담을 공간이다.
static int[] dp2; //감소하는 부분수열을 담을 공간이다.
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
int N = Integer.parseInt(br.readLine());
ary = new int[N];
dp1 = new int[N];
dp2 = new int[N];
//수열 A를 입력받는다.
st = new StringTokenizer(br.readLine());
for(int i=0;i<N;++i){
ary[i] = Integer.parseInt(st.nextToken());
}
increase_length(N-1);
decrease_length(N-1);
//증가 추세와 감소 추세를 담은 dp1, dp2 배열의 합이 바이토닉 부분 수열이다.
//이 중 가장 긴 바이토닉 부분 수열을 찾는다.
//본인 자신이 2번 카운팅 되기 때문에 -1을 해준다.
int in_de_length = 0;
for(int i=0;i<N;++i){
in_de_length = Math.max(in_de_length, dp1[i]+dp2[i]-1);
}
System.out.println(in_de_length);
}
//N 이전의 증가 배열의 부분수열 중 가장 큰 길이의 값
private static int increase_length(int N) {
//j<i
//dp 계산을 할 때, 그 직전 값을 계산해야 하므로, 0부터 시작을 해야한다.
for(int i=0;i<=N;++i){
//dp1[i]의 초기값은 1이다.
//기본적으로 자기 자신이 있으므로 부분 수열의 길이는 1이다.
dp1[i] = 1;
for(int j=0;j<i;++j) {
//본인보다 작은값일 경우와
//dp값이 본인보다 크거나 같을 경우 (제일 큰 값으로 유지해야 되므로)
//dp를 갱신해준다.
if (ary[j] < ary[i] && dp1[i] <= dp1[j]){
dp1[i] = dp1[j] + 1;
}
}
}
return dp1[N];
}
//N 이후의 감소 배열의 부분수열 중 가장 큰 길이의 값
private static int decrease_length(int N) {
//j>i
//dp 계산을 할 때, i를 기준으로 뒤에서 계산을 해야하므로 N부터 계산을 해야한다.
for(int i=N;i>=0;--i){
//dp1[i]의 초기값은 1이다.
//기본적으로 자기 자신이 있으므로 부분 수열의 길이는 1이다.
dp2[i] = 1;
for(int j= ary.length-1;j>i;--j){
//i를 기준으로 그 뒤의 값(j>i)을 계산해야 하므로,
//dp값이 본인보다 크거나 같을 경우 (제일 큰 값으로 유지해야 되므로)
//dp2를 갱신해준다.
if(ary[j] < ary[i] && dp2[i] <= dp2[j]){
dp2[i] = dp2[j] + 1;
}
}
}
return dp2[N];
}
}