문제 소개
이 글에서는 정수 배열이 주어졌을 때 증가하는 부분 수열(increasing subsequence)의 원소들을 곱하여 얻을 수 있는 최댓값을 구하는 프로그램을 C++로 작성해 보겠습니다.
여기서 부분 수열(subsequence)이란 배열에서 원소들의 상대적인 순서를 유지한 채 일부 원소를 선택한 것을 의미합니다. 단, 선택된 원소들은 반드시 왼쪽에서 오른쪽으로 값이 커져야 하며, 부분 수열의 길이에는 제한이 없습니다.
예시로 이해하기
배열이 {3, 100, 4, 5, 150, 6}라고 가정해 봅시다. 이 배열에서 만들 수 있는 증가 부분 수열 중 하나는 3, 100, 150입니다. 이 수열의 곱은 3 × 100 × 150 = 45,000이며, 가능한 모든 조합을 비교해 보아도 이 값이 가장 큽니다. 따라서 정답은 45000입니다.
풀이 접근: 동적 계획법(Dynamic Programming)
이 문제는 잘 알려진 LIS(최장 증가 부분 수열) 알고리즘과 거의 같은 구조를 가지고 있습니다. 핵심 아이디어는 다음과 같습니다.
- 상태 정의:
mpis[i]= 인덱스i의 원소로 끝나는 증가 부분 수열의 최대 곱 - 초기값:
mpis[i] = arr[i]— 자기 자신 하나만으로 이루어진 부분 수열 - 점화식:
j < i이면서arr[i] > arr[j]인 모든j에 대해mpis[i] = max(mpis[i], mpis[j] × arr[i]) - 최종 답:
mpis배열 전체 요소 중 최댓값
C++ 구현 코드
#include <bits/stdc++.h>
#define ll long long int
using namespace std;
// 증가 부분 수열의 최대 곱을 반환하는 함수
ll lis(ll arr[], ll n) {
ll mpis[n];
// 초기값 설정: 각 원소 자신으로 초기화
for (int i = 0; i < n; i++)
mpis[i] = arr[i];
// 동적 계획법으로 최대 곱 갱신
for (int i = 1; i < n; i++)
for (int j = 0; j < i; j++)
if (arr[i] > arr[j] && mpis[i] < (mpis[j] * arr[i]))
mpis[i] = mpis[j] * arr[i];
// mpis 배열 전체에서 최댓값 반환
return *max_element(mpis, mpis + n);
}
int main() {
ll arr[] = { 3, 100, 4, 5, 150, 6 };
ll n = sizeof(arr) / sizeof(arr[0]);
printf("%lld", lis(arr, n));
return 0;
}
실행 결과
45000
단계별 동작 과정
입력 배열 {3, 100, 4, 5, 150, 6}에 대해 DP 테이블이 채워지는 과정은 다음과 같습니다.
mpis[0] = 3— 시작 원소mpis[1] = 3 × 100 = 300mpis[2] = 3 × 4 = 12mpis[3] = 12 × 5 = 60mpis[4] = 300 × 150 = 45000← 최댓값mpis[5] = 60 × 6 = 360
배열 전체에서 가장 큰 값인 45000이 최종 결과가 됩니다.
복잡도 분석
- 시간 복잡도: O(n²) — 각 원소마다 앞선 모든 원소와 비교
- 공간 복잡도: O(n) — DP 테이블 저장 공간
n이 매우 큰 경우 세그먼트 트리 등을 활용하면 더 빠르게 최적화할 수 있지만, 대부분의 경우 위의 O(n²) 동적 계획법 풀이로 충분합니다.