publicclassLIS_DP{ publicstaticvoidmain(String[] args){ int[] list = {3, 2, 6, 4, 5, 1}; int[] LIS = newint[list.length]; // i 번째 숫자를 마지막 숫자로 사용할 경우 최장 증가 수열의 길이를 저장할 배열
for (int i = 0; i < LIS.length; i++) { LIS[i] = 1; for (int j = 0; j < i; j++) { if (list[j] < list[i] && LIS[i] < LIS[j] + 1){ LIS[i] = LIS[j] + 1; } } }
System.out.println(Arrays.toString(LIS));
// 최대 값을 찾기 int maxLISIndex = 0; for (int i = 0; i < LIS.length; i++) { if(LIS[maxLISIndex] < LIS[i]){ maxLISIndex = i; } } System.out.println("최장 증가 수열의 길이 : " + LIS[maxLISIndex]); } }
핵심코드는 이 부분 입니다.
1 2 3 4 5 6 7 8 9 10
for (int i = 0; i < LIS.length; i++) { LIS[i] = 1; // 모든 배열을 값을 1로 초기화 for (int j = 0; j < i; j++) { // 내 앞의 숫자 중에 작은 숫자들을 찾기 if (list[j] < list[i] && LIS[i] < LIS[j] + 1){ // 나를 기준으로 내앞의 원소가 작으며, // 내가 들고있는 최장 증가 수열의 길이보다 그 값(+1)을 넣은 값이 크다면 LIS[i] = LIS[j] + 1; } } }
publicclassLIS_DP_Path{ publicstaticvoidmain(String[] args){ int[] list = {3, 2, 6, 4, 5, 1}; int[] LIS = newint[list.length]; // i 번째 숫자를 마지막 숫자로 사용할 경우 최장 증가 수열의 길이를 저장할 배열 int[] path = newint[list.length]; // 경로를 역추적 할 배열
for (int i = 0; i < LIS.length; i++) { LIS[i] = 1; // 모든 배열을 값을 1로 초기화 path[i] = -1; // 내 앞의 수열 숫자의 index for (int j = 0; j < i; j++) { // 내 앞의 숫자 중에 작은 숫자들을 찾기 if (list[j] < list[i] && LIS[i] < LIS[j] + 1) { // 나를 기준으로 내앞의 원소가 작으며, // 내가 들고있는 최장 증가 수열의 길이보다 그 값(+1)을 넣은 값이 크다면 LIS[i] = LIS[j] + 1; path[i] = j; // 내 앞 수열을 찾았기 때문에 그 index 저장 } } }
// 최대 값을 찾기 int maxLISIndex = 0; for (int i = 0; i < LIS.length; i++) { if (LIS[maxLISIndex] < LIS[i]) { maxLISIndex = i; } } System.out.println("최장 증가 수열의 길이 : " + LIS[maxLISIndex]);
System.out.println("Path : " + Arrays.toString(path)); StringBuilder lisPath = new StringBuilder(); for (int i = maxLISIndex; i != -1; i = path[i]) { // 최장 증가 수열의 index번호를 기준으로 앞으로 이동하며 -1을 만날 때 까지 출력 lisPath.insert(0, list[i] + " "); // 제일 처음으로 삽입 } System.out.println(lisPath.toString()); } }
이 코드의 기본 원리는 내 앞의 수열 Index를 저장하며 마지막에 역추적한다.에 의미를 두고 있습니다.
1 2 3 4 5 6 7 8 9 10
for (int i = 0; i < LIS.length; i++) { ... path[i] = -1; // 내 앞의 수열 숫자의 index for (int j = 0; j < i; j++) { // 내 앞의 숫자 중에 작은 숫자들을 찾기 if (list[j] < list[i] && LIS[i] < LIS[j] + 1) { ... path[i] = j; // 내 앞 수열을 찾았기 때문에 그 index 저장 } } }
내 앞의 숫자를 탐색하며 작은 숫자를 찾을 때 조금의 코드를 추가해주면서 작성할 수 있습니다. path[i] = j;를 작성해주며 경로를 작성 할 수 있으며, 결과값을 보도록 하겠습니다.
3, 4, 5가 나왔지만, 2, 4, 5 도 됩니다. 프로그램 상 앞으로 현재값을 기준으로 찾기 때문입니다.
좋습니다. 이렇게 최장 증가 수열에 대해서 알아봤지만, 이 알고리즘의 경우 O(n^2)의 시간 복잡도를 갖고 있습니다.
i를 n번 돌리며 j가 0부터 i까지 확인 하기 때문에.
좀 더 씬빡 한방법을 생각해보도록 하겠습니다.
Binary Search를 통해 LIS 찾기
Binary Search로 LIS를 어떻게 찾는지 예시를 통해 확인해보도록 하겠습니다.
8, 2, 4, 3, 6, 11, 7, 10, 14, 5 라는 수열이 있다고 가정해 보겠습니다.
LIS List는 LIS로 사용가능한 숫자를 저장하는 List라고 하겠으며, LIS[i]는 가장 작은 값을 LIS[i]에 저장한다고 하겠습니다.
8
2
4
3
6
11
7
10
14
5
8
2
2
2
2
2
2
2
2
2
4
3
3
3
3
3
3
3
6
6
6
6
6
5
11
7
7
7
7
10
10
10
14
14
진행 순서는 다음과 같습니다.
1 2 3 4 5 6 7 8 9 10
1 : 8을 LIS에 넣음 2 : 2를 LIS 이진 탐색을 통해 넣을 위치를 확인(2보다 작은 값 찾지 못함) -> -1을 리턴하며 0번째에 입력 3 : 4를 LIS 이진 탐색을 통해 넣을 위치를 확인(4보다 작은 값 2 위치를 -부호로) -> -2를 리턴하며 1번째에 입력 4 : 3를 LIS 이진 탐색을 통해 넣을 위치를 확인(3보다 작은 값 2 위치를 -부호로) -> -2를 리턴하며 1번째에 입력 4 : 6를 LIS 이진 탐색을 통해 넣을 위치를 확인(6보다 작은 값 3 위치를 -부호로) -> -3를 리턴하며 2번째에 입력 5 : 11를 LIS 이진 탐색을 통해 넣을 위치를 확인(11보다 작은 값 6 위치를 -부호로) -> -4를 리턴하며 3번째에 입력 6 : 7를 LIS 이진 탐색을 통해 넣을 위치를 확인(7보다 작은 값 6 위치를 -부호로) -> -4를 리턴하며 3번째에 입력 7 : 10를 LIS 이진 탐색을 통해 넣을 위치를 확인(10보다 작은 값 7 위치를 -부호로) -> -5를 리턴하며 4번째에 입력 8 : 14를 LIS 이진 탐색을 통해 넣을 위치를 확인(14보다 작은 값 10 위치를 -부호로) -> -6를 리턴하며 5번째에 입력 9 : 5를 LIS 이진 탐색을 통해 넣을 위치를 확인(5보다 작은 값 3 위치를 -부호로) -> -3를 리턴하며 2번째에 입력
이렇게 최장 증가 수열을 구할 수 있게 됩니다. 그럼 코드로 한번 작성해 보도록 하겠습니다.