이번 포스팅에서는 백준 13398번: 연속합 2 문제를 해결하면서 겪었던 시행착오와 개선 과정을 공유하고자 함. ✨
문제에서는 n
개의 정수로 이루어진 수열이 주어짐.
연속된 구간의 합 중 최대값을 구해야 하는데, 단 한 개의 원소를 제거할 수 있는 조건이 추가됨. 😲
(단, 적어도 한 개의 원소는 선택해야 함!)
예를 들어,
10 -4 3 1 5 6 -35 12 21 -1
54
-35
를 제거했을 때 최대 연속합이 54가 됨. 🎯 처음에는 누적합(Prefix Sum)을 이용해 연속 구간의 합을 빠르게 계산하려고 함.
누적합을 사용하면
[
P[i] = arr[1] + arr[2] + \cdots + arr[i]
]
로 구간 합을
[
P[j] - P[i-1]
]
로 빠르게 계산할 수 있음.
🔻 하지만 한 개의 원소 제거 조건을 처리하려고 할 때, 예상치 못한 문제가 발생함.
누적합을 사용하면 연속 구간의 합을 쉽게 구할 수 있지만, 구간 내 특정 원소를 제거하는 방법이 너무 복잡해짐.
n
이 최대 100,000
이라서 시간 초과 가능성이 큼. 💡 결국 누적합만으로는 "제거" 조건을 효율적으로 반영하기 어려움이라는 결론에 도달함.
💡 문제 해결의 핵심은 한 개의 원소를 제거하는 경우와 그렇지 않은 경우를 동시에 고려하는 것임.
따라서, DP 배열을 두 가지 상태로 관리함. 📊
dp[i][0]
: 인덱스 i
까지 고려했을 때, 아직 원소 제거를 사용하지 않은 상태에서 끝나는 연속 부분합의 최대값 dp[i][1]
: 인덱스 i
까지 고려했을 때, 이미 한 개의 원소를 제거한 상태에서 끝나는 연속 부분합의 최대값 dp[i][0] = Math.max(dp[i - 1][0] + arr[i], arr[i]);
dp[i][1] = Math.max(dp[i - 1][0], dp[i - 1][1] + arr[i]);
✔ dp[i][0]
기존의 카데인 알고리즘(Kadane's Algorithm)과 동일하게, 현재 원소를 포함할 때
✔ dp[i][1]
dp[i-1][0]
)에서 현재 원소를 제거하는 경우 dp[i-1][1]
)에서 현재 원소를 계속 더하는 경우 이 두 가지를 비교하여 최댓값을 갱신함.
dp[0][0]
의 중요성코드를 처음 작성할 때, dp[0][0]
을 올바르게 초기화하지 않아서 오답이 발생함. 😵💫
dp[0][0]
에 초기값을 넣지 않으면
dp[1][0] = Math.max(dp[0][0] + arr[1], arr[1]);
에서 올바른 비교가 이루어지지 않음.
dp[0][0]
에 매우 작은 값을 할당하여 계산에 영향을 주지 않도록 처리함.
dp[0][0] = -987654321;
이렇게 하면 첫 번째 원소부터 올바르게 DP 점화식을 적용할 수 있음. 🎯
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
private static StringTokenizer st;
private static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
private static int N;
private static int[] arr;
private static int[][] dp;
public static void main(String[] args) throws IOException {
inputSetting();
System.out.println(getAnswer());
}
private static int getAnswer() {
int answer = Integer.MIN_VALUE;
dp[0][0] = -987654321; // 초기화 필수!
for (int i = 1; i <= N; i++) {
dp[i][0] = Math.max(dp[i - 1][0] + arr[i], arr[i]);
dp[i][1] = Math.max(dp[i - 1][0], dp[i - 1][1] + arr[i]);
answer = Math.max(answer, Math.max(dp[i][0], dp[i][1]));
}
return answer;
}
private static void inputSetting() throws IOException {
N = Integer.parseInt(br.readLine());
arr = new int[N + 1];
dp = new int[N + 1][2];
st = new StringTokenizer(br.readLine());
for (int i = 1; i <= N; i++) {
arr[i] = Integer.parseInt(st.nextToken());
}
}
}
✔ 누적합 방식의 한계 🚫
✔ DP 접근의 장점 ✅
✔ 초기화의 중요성 💡
dp[0][0]
을 올바르게 초기화하지 않으면 이후 계산이 꼬일 수 있음.