
이번 문제는 마지막 곡의 최대 볼륨을 구하는 문제로,
1. i번째를 연주하려면, 이전의 볼륨에서 정해진 변경 볼륨대로 + or -를 해야한다.
2. 볼륨이 0 <= volume <= M 을 충족해야 한다.
3. 모든 곡을 리스트에 적힌 순서대로 연주해야 한다.
가장 먼저 DFS를 통해 최대값을 도출하려 했습니다.
import java.util.*;
import java.io.*;
public class Main {
static int answer = -1;
public static void main(String[] args) throws Exception {
BufferedReader br =
new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st =
new StringTokenizer(br.readLine());
int seq = Integer.parseInt(st.nextToken());
int start = Integer.parseInt(st.nextToken());
int limit = Integer.parseInt(st.nextToken());
int[] arr = new int[seq];
st = new StringTokenizer(br.readLine());
for(int i = 0; i < seq; i++) {
arr[i] = Integer.parseInt(st.nextToken());
}
dfs(arr, start, limit, 0, seq);
System.out.println(answer);
}
public static void dfs(int[] arr, int start, int limit, int idx, int seq) {
if(idx == seq) {
answer = Math.max(answer, start);
return;
}
if(start - arr[idx] >= 0) {
dfs(arr, start - arr[idx], limit, idx + 1, seq);
}
if(start + arr[idx] <= limit) {
dfs(arr, start + arr[idx], limit, idx + 1, seq);
}
}
}
그리고 당연하게도... 시간초과가 발생했습니다.

이유는 바로 상태저장 없이 DFS를 진행했기 때문입니다.
아래 그림을 통해 더 자세히 설명하자면

💡 무조건 증감을 시도하기 때문에, 항상 O(2^N)의 시간 복잡도가 생기면서 시간초과 발생
이에, 중복되는 상황을 제거하기 위해, 2차원 boolean배열을 통해 중복되는 접근을 차단했습니다.
import java.util.*;
import java.io.*;
public class Main {
static int answer = -1;
public static void main(String[] args) throws Exception {
BufferedReader br =
new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st =
new StringTokenizer(br.readLine());
int seq = Integer.parseInt(st.nextToken());
int start = Integer.parseInt(st.nextToken());
int limit = Integer.parseInt(st.nextToken());
boolean[][] v = new boolean[seq + 1][limit + 1];
int[] arr = new int[seq];
st = new StringTokenizer(br.readLine());
for(int i = 0; i < seq; i++) {
arr[i] = Integer.parseInt(st.nextToken());
}
dfs(arr, start, limit, 0, seq, v);
System.out.println(answer);
}
public static void dfs(int[] arr, int start, int limit, int idx, int seq, boolean[][] v) {
if(idx == seq) {
answer = Math.max(answer, start);
return;
}
if(start - arr[idx] >= 0 && !v[idx + 1][start - arr[idx]]) {
v[idx + 1][start - arr[idx]] = true;
dfs(arr, start - arr[idx], limit, idx + 1, seq, v);
}
if(start + arr[idx] <= limit && !v[idx + 1][start + arr[idx]]) {
v[idx + 1][start + arr[idx]] = true;
dfs(arr, start + arr[idx], limit, idx + 1, seq, v);
}
}
}
그리고 결과는... 정답이 였습니다.

아래의 그림으로 성공하는 이유를 설명하자면,

💡 이전에는 중복이 가능했지만, 2차원 배열을 통해 이미 방문한 곳을 차단했기 때문에 중복 없이 탐색이 가능!
-> 시간복잡도가 O(N * (M + 1)) 로 대폭감소 / N = 곡 수, M = 최대 볼륨
정답 후 다른 사람들의 정답을 찾아 파악해봤는데, 대부분 DP를 통해 문제해결을 도모. 차이점 비교를 위해서 코드를 작성해봤습니다.
import java.util.*;
import java.io.*;
public class Main {
static int answer = -1;
public static void main(String[] args) throws Exception {
BufferedReader br =
new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st =
new StringTokenizer(br.readLine());
int seq = Integer.parseInt(st.nextToken());
int start = Integer.parseInt(st.nextToken());
int limit = Integer.parseInt(st.nextToken());
int[] arr = new int[seq];
boolean[][] dp = new boolean[seq + 1][limit + 1];
dp[0][start] = true;
st = new StringTokenizer(br.readLine());
for(int i = 0; i < seq; i++) {
arr[i] = Integer.parseInt(st.nextToken());
}
for(int i = 0; i < seq; i++) {
for(int j = 0; j < limit + 1; j++) {
if(dp[i][j]) {
if(j + arr[i] <= limit) {
dp[i + 1][j + arr[i]] = true;
}
if(j - arr[i] >= 0) {
dp[i + 1][j - arr[i]] = true;
}
}
}
}
int answer = -1;
for(int j = limit; j >= 0; j--) {
if(dp[seq][j]) {
answer = j;
break;
}
}
System.out.println(answer);
}
}

DP를 풀면서 이전의 DFS + 2차원 배열 코드와 다른 점으로는
였습니다.
더 나아가, 1차원 배열로도 진행이 가능합니다.
import java.util.*;
import java.io.*;
public class Main {
static int answer = -1;
public static void main(String[] args) throws Exception {
BufferedReader br =
new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st =
new StringTokenizer(br.readLine());
int seq = Integer.parseInt(st.nextToken());
int start = Integer.parseInt(st.nextToken());
int limit = Integer.parseInt(st.nextToken());
int[] arr = new int[seq];
boolean[] preDP = new boolean[limit + 1];
boolean[] nextDP = new boolean[limit + 1];
preDP[start] = true;
st = new StringTokenizer(br.readLine());
for(int i = 0; i < seq; i++) {
arr[i] = Integer.parseInt(st.nextToken());
}
for(int z = 0; z < seq; z++) {
Arrays.fill(nextDP, false);
for(int i = 0; i < limit + 1; i++) {
if(preDP[i]) {
if(i + arr[z] <= limit) {
nextDP[i + arr[z]] = true;
}
if(i - arr[z] >= 0) {
nextDP[i - arr[z]] = true;
}
}
}
boolean[] temp = nextDP;
nextDP = preDP;
preDP = temp;
}
int answer = -1;
for(int j = limit; j >= 0; j--) {
if(preDP[j]) {
answer = j;
break;
}
}
System.out.println(answer);
}
}

이전의 2차원 배열 DP과 지금의 1차원 배열이 다른 점으로는
💡 이를 통해, 공간 복잡도를 O(N * M) → O(2M)로 줄일 수 있게 됩니다!