최장증가부분수열 Longest Increasing Subsequence
문제 예시)
수열 A가 주어졌을 때, 가장 긴 증가하는 부분 수열을 구하는 프로그램을 작성하시오.
예를 들어, 수열 A = {10, 20, 10, 30, 20, 50} 인 경우에 가장 긴 증가하는 부분 수열은 A = {10, 20, 10, 30, 20, 50} 이고, 길이는 4이다.
LIS를 해결하는 방법은 두가지가 있다. N의 크기, 문제의 조건에 따라 아래 두 방법중 적절한 것을 사용하면 된다.
방법 1 : 이중 for-loop
시간복잡도 : O(n^2)
dp 배열은 i 번째 원소가 가질 수 있는 최대증가수열의 길이를 뜻한다.
수열 A = {10, 20, 10, 30, 20, 50} 인 경우 dp[4]는 3이다. {10, 20, 10, 30, 20 ...}
n이 10,000을 넘어가면 시간초과가 발생할 수 있다.
int arr[MAX]; // 인덱스마다 각 입력값 int dp[MAX]; // 인덱스마다 각 증가 수열의 길이 int max = 0; dp[0] = 1; for(int i=1;i<N;i++) { dp[i] = 1; // i 를 기준으로 인덱스 0 에서부터 i-1까지 체크한다 // 길이를 기준 for(int j=0;j<i;j++) { if (arr[i] > arr[j] && dp[j] + 1 > dp[i]) { // 증가 수열 dp[i] = dp[j] + 1; } } if (max < dp[i]) { max = dp[i]; } }
방법 2 : 이분 탐색
시간복잡도 : O(nlogn)
배열 마지막 요소보다 새로 들어오는 수가 크다면, 배열에 넣는다.
그렇지 않다면, 그 수가 들어갈 자리에 들어가 있는 값을 교체한다(lower_bound 로 들어갈 자리를 찾음)
lower_bound : 주어진 값보다 작지 않은(같거나 큰) 첫번째 원소의 iterator를 반환
dp 벡터의 사이즈가 LIS가 된다
단점 : 정답밖에 모른다. 경로를 역추적 할 수 없다.
#include<iostream>
#include<algorithm>
#include<vector>
#define endl "\n"
using namespace std;
// LIS로 풀자
int n;
int answer;
vector<int>v;
vector<int>lis; // 인덱스마다 각 증가 수열의 길이
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
freopen("input.txt", "r", stdin);
cin >> n;
for (int i = 0; i < n; i++) {
int tmp; cin >> tmp;
v.push_back(tmp);
}
lis.push_back(v[0]);
for (int i = 1; i < n; i++) {
if (lis.back() < v[i]) { // lis의 맨 뒤의 원소보다 큰 값이 들어오면 삽입
lis.push_back(v[i]);
}
else { // lis의 맨 뒤 원소보다 작은 값이면, 그 원소가 들어갈 위치에 삽입
int idx = lower_bound(lis.begin(), lis.end(), v[i]) - lis.begin();
lis[idx] = v[i];
}
}
answer = lis.size();
cout << answer;
return 0;
}