메모리: 139204 KB, 시간: 620 ms
이분 탐색, 가장 긴 증가하는 부분 수열: O(n log n)
2023년 11월 16일 11:27:11
수열 A가 주어졌을 때, 가장 긴 증가하는 부분 수열을 구하는 프로그램을 작성하시오.
예를 들어, 수열 A = {10, 20, 10, 30, 20, 50} 인 경우에 가장 긴 증가하는 부분 수열은 A = {10, 20, 10, 30, 20, 50} 이고, 길이는 4이다.
첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000,000)이 주어진다.
둘째 줄에는 수열 A를 이루고 있는 Ai가 주어진다. (1 ≤ Ai ≤ 1,000,000)
첫째 줄에 수열 A의 가장 긴 증가하는 부분 수열의 길이를 출력한다.
var fs = require('fs');
var input = fs.readFileSync('/dev/stdin').toString().split('\n');
const N = +input.shift();
const array = input[0].split(' ').map(Number);
let lis = [array[0]];
// 이분 탐색으로 배열에서 가장 num과 가까운 수를 찾는다.
const findClose = (start, end, num) => {
// 끝 포인터가 시작 포인터보다 커지면 종료
while (start < end) {
// 중간 인덱스와 값
let mid = Math.floor((start + end) / 2);
const midValue = lis[mid];
if (midValue >= num) {
// 중간 값보다 num이 작으면 끝 포인터를 줄여서 다시 검색한다.
end = mid;
} else {
// 중간 값보다 num이 크면 시작 포인터를 키워서 다시 검색한다.
start = mid + 1;
}
}
return start;
};
for (let i = 1; i < array.length; i++) {
let num = array[i];
let peek = lis[lis.length - 1];
if (num > peek) {
// LIS 배열의 끝 요소가 새로 들어오는 것보다 작으면 그냥 넣는다.
lis.push(num);
} else {
// 아니면 새로 들어온 요소와 가장 가까운 숫자를 찾아 교체 해준다.
let closeIndex = findClose(0, lis.length - 1, num);
lis[closeIndex] = num;
}
}
// 결국에는 가장 큰 길이의 수열이 남게 된다.. (값 X, 길이만)
console.log(lis.length);
가장 긴 수열의 길이를 찾는 것은 클래식한 알고리즘으로 LIS (Longest Increasing Subsequence) 알고리즘 이라고 한다.
이 알고리즘을 푸는 방법으로는 대표적으로 3가지가 있다.
나는 이 중에 3번 이분 탐색 방법으로 풀었다. 내가 푼 방법은 가장 큰 수열 자체를 찾는 것이 아닌, 가장 큰 수열의 길이만 찾을 수 있다..!!
역시나 처음에 도저히 풀이 방법이 떠오르지 않아 열심히 구글링과 유튜브 해설 영상을 찾아봤다..
아래는 가장 도움이 된 유튜브와 블로그다.
참조 블로그: https://shoark7.github.io/programming/algorithm/3-LIS-algorithms