Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

누에나방애벌레

최장 증가 수열 (LIS, Longest Increasing Subsequence) 본문

공부/알고리즘

최장 증가 수열 (LIS, Longest Increasing Subsequence)

명석 2024. 9. 5. 10:21

1. LIS (Longest Increasing Subsequence)의 정의

최장 증가 부분 수열(LIS)이란, 주어진 수열에서 몇 개의 숫자를 골라서 만들 수 있는 가장 긴 증가하는 부분 수열을 의미합니다. 단, 부분 수열은 원래 수열에서 순서를 유지해야 합니다. 예를 들어, 수열이 {6, 2, 5, 1, 7, 4, 8, 3}이라면, 이 수열에서 LIS는 {2, 5, 7, 8}로 길이가 4가 됩니다.

LIS 문제는 다양한 알고리즘으로 해결할 수 있지만, 이 중 이진 탐색을 활용하여 O(N log N) 시간 복잡도로 해결하는 방법이 가장 효율적입니다.

2. 문제 해결 코드

아래는 LIS 문제를 해결하는 C++ 코드입니다. 이 코드는 이진 탐색을 통해 효율적으로 LIS의 길이를 구하고 실제 LIS 수열을 추적하고 출력하는 방식입니다.

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 이진 탐색 함수: 정렬된 배열에서 num 이상의 첫 번째 위치를 찾음
// lower_bound 함수를 통해서 사용 가능
int binary_search(vector<int>& lis, int num) {
	int low = 0, high = lis.size();

	while (low < high) {
		int mid = low + (high - low) / 2;
		if (lis[mid] < num) {
			low = mid + 1;
		}
		else {
			high = mid;
		}
	}
	return low;
}

int main() {
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);

	int N;
	cin >> N;

	vector<int> arr(N);  // 입력된 수열
	vector<int> lis;  // LIS를 저장할 벡터
	vector<int> pos(N);  // 각 숫자가 LIS에서 들어간 위치를 기록할 배열
	vector<int> trace(N, -1);  // 이전 위치 추적을 위한 배열

	// 입력 받기
	for (int i = 0; i < N; i++) {
		cin >> arr[i];
	}

	// LIS 구하기
	for (int i = 0; i < N; i++) {
		int num = arr[i];
		int pos_in_lis = binary_search(lis, num);  // 이진 탐색을 통해 LIS에서 들어갈 위치를 찾음
		

		if (pos_in_lis == lis.size()) {
			lis.push_back(num);  // 새로운 숫자를 LIS의 끝에 추가
		}
		else {
			lis[pos_in_lis] = num;  // 해당 위치의 값을 갱신
		}

		pos[i] = pos_in_lis;  // 해당 숫자가 들어간 위치 기록

		// trace 배열로 이전 요소 추적
		if (pos_in_lis > 0) {
			trace[i] = pos_in_lis - 1;
		}
	}

	// LIS 복원하기
	vector<int> actual_lis(lis.size());
	int current_pos = lis.size() - 1;
	for (int i = N - 1; i >= 0; i--) {
		if (pos[i] == current_pos) {
			actual_lis[current_pos] = arr[i];
			current_pos--;
		}
	}

	// 결과 출력: LIS 길이와 실제 LIS
	cout << "LIS 길이: " << lis.size() << "\n";
	cout << "LIS: ";
	for (int num : actual_lis) {
		cout << num << " ";
	}
	cout << "\n";

	return 0;
}
 

3. 동작 과정

이 코드는 주어진 수열에서 이진 탐색을 사용하여 LIS의 길이를 구하고, 각 수가 LIS에서 차지하는 위치를 기록하며, 이를 바탕으로 실제 LIS 수열을 복원하는 방식으로 동작합니다.

 

입력:

8
6 2 5 1 7 4 8 3

 

초기 설정

  • lis: LIS 배열로, 실제 증가하는 부분 수열을 기록합니다.
  • pos: 각 수가 LIS 배열의 어느 위치에 삽입되었는지 기록합니다.
  • trace: LIS 수열을 복원하기 위해, 각 수가 어디서 왔는지를 기록합니다.

각 단계별 lis, pos, trace 배열 변화

1) 첫 번째 수: 6

  • lis에 6을 추가합니다.
  • lis 상태: [6]
  • pos 상태: [0]
  • trace 상태: [-1]

2) 두 번째 수: 2

  • lis[0] 위치에 2를 덮어씁니다.
  • lis 상태: [2]
  • pos 상태: [0, 0]
  • trace 상태: [-1, -1]

3) 세 번째 수: 5

  • lis에 5를 추가합니다.
  • lis 상태: [2, 5]
  • pos 상태: [0, 0, 1]
  • trace 상태: [-1, -1, 0]

4) 네 번째 수: 1

  • lis[0] 위치에 1을 덮어씁니다.
  • lis 상태: [1, 5]
  • pos 상태: [0, 0, 1, 0]
  • trace 상태: [-1, -1, 0, -1]

5) 다섯 번째 수: 7

  • lis에 7을 추가합니다.
  • lis 상태: [1, 5, 7]
  • pos 상태: [0, 0, 1, 0, 2]
  • trace 상태: [-1, -1, 0, -1, 1]

6) 여섯 번째 수: 4

  • lis[1] 위치에 4를 덮어씁니다.
  • lis 상태: [1, 4, 7]
  • pos 상태: [0, 0, 1, 0, 2, 1]
  • trace 상태: [-1, -1, 0, -1, 1, 0]

7) 일곱 번째 수: 8

  • lis에 8을 추가합니다.
  • lis 상태: [1, 4, 7, 8]
  • pos 상태: [0, 0, 1, 0, 2, 1, 3]
  • trace 상태: [-1, -1, 0, -1, 1, 0, 2]

8) 여덟 번째 수: 3

  • lis[1] 위치에 3을 덮어씁니다.
  • lis 상태: [1, 3, 7, 8]
  • pos 상태: [0, 0, 1, 0, 2, 1, 3, 1]
  • trace 상태: [-1, -1, 0, -1, 1, 0, 2, 0]

LIS 복원 과정

trace 배열을 이용해 실제 LIS 수열을 복원하는 과정입니다.

lis의 마지막 인덱스인 3에서 시작하여, trace 배열을 따라갑니다.

  • pos[6] = 3 → arr[6] = 8 → LIS에 8 추가.
  • pos[4] = 2 → arr[4] = 7 → LIS에 7 추가.
  • pos[2] = 1 → arr[2] = 5 → LIS에 5 추가.
  • pos[1] = 0 → arr[1] = 2 → LIS에 2 추가.

최종적으로 구해진 LIS는 {2, 5, 7, 8}입니다.


예시 문제: 백준 12015번

백준 12015 - 가장 긴 증가하는 부분 수열 2 문제는 바로 이 LIS 문제를 이진 탐색을 활용해 풀 수 있는 대표적인 문제입니다.

 

문제 요약:

주어진 수열에서 최장 증가 부분 수열(LIS)의 길이를 구하는 문제입니다.

 

입력 예시:

6
10 20 10 30 20 50

 

출력 예시:

4

 

이 문제에서 최장 증가 부분 수열은 {10, 20, 30, 50}이므로, 그 길이는 4가 됩니다.

백준 12015번 문제는 위에서 설명한 이진 탐색을 사용한 방식으로 효율적으로 해결할 수 있습니다.

'공부 > 알고리즘' 카테고리의 다른 글

후위 표기법(Shunting Yard Algorithm)  (0) 2024.09.06
2차원 배열의 회전  (0) 2024.08.22
1차원 배열의 회전  (0) 2024.08.03
재귀 함수  (0) 2024.07.26
Greedy 알고리즘  (0) 2024.07.26