Skip to content

Add LongestIncreasingSubsequenceNLogN class and corresponding test class #6221

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.thealgorithms.dynamicprogramming;

/**
* Implementation of the Longest Increasing Subsequence (LIS) problem using
* an O(n log n) dynamic programming solution enhanced with binary search.
*
* @author Vusal Huseynov (https://github.com/huseynovvusal)
*/
public final class LongestIncreasingSubsequenceNLogN {
private LongestIncreasingSubsequenceNLogN() {
}

/**
* Finds the index of the smallest element in the array that is greater than
* or equal to the target using binary search. The search is restricted to
* the first `size` elements of the array.
*
* @param arr The array to search in (assumed to be sorted up to `size`).
* @param size The number of valid elements in the array.
* @param target The target value to find the lower bound for.
* @return The index of the lower bound.
*/
private static int lowerBound(int[] arr, int target, int size) {
int l = 0;
int r = size;

while (l < r) {
int mid = l + (r - l) / 2;

if (target > arr[mid]) {
// Move right if target is greater than mid element
l = mid + 1;
} else {
// Move left if target is less than or equal to mid element
r = mid;
}
}

// Return the index where the target can be inserted
return l;
}

/**
* Calculates the length of the Longest Increasing Subsequence (LIS) in the given array.
*
* @param arr The input array of integers.
* @return The length of the LIS.
*/
public static int lengthOfLIS(int[] arr) {
if (arr == null || arr.length == 0) {
return 0; // Return 0 for empty or null arrays
}

// tails[i] - the smallest end element of an increasing subsequence of length i+1
int[] tails = new int[arr.length];
// size - the length of the longest increasing subsequence found so far
int size = 0;

for (int x : arr) {
// Find the position to replace or extend the subsequence
int index = lowerBound(tails, x, size);

// Update the tails array with the current element
tails[index] = x;

// If the element extends the subsequence, increase the size
if (index == size) {
size++;
}
}

// Return the length of the LIS
return size;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.thealgorithms.dynamicprogramming;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

public class LongestIncreasingSubsequenceNLogNTest {

private static Stream<Arguments> provideTestCases() {
return Stream.of(Arguments.of(new int[] {10, 9, 2, 5, 3, 7, 101, 18}, 4), Arguments.of(new int[] {0, 1, 0, 3, 2, 3}, 4), Arguments.of(new int[] {7, 7, 7, 7, 7}, 1), Arguments.of(new int[] {1, 3, 5, 4, 7}, 4), Arguments.of(new int[] {}, 0), Arguments.of(new int[] {10}, 1),
Arguments.of(new int[] {3, 10, 2, 1, 20}, 3), Arguments.of(new int[] {50, 3, 10, 7, 40, 80}, 4));
}

@ParameterizedTest
@MethodSource("provideTestCases")
public void testLengthOfLIS(int[] input, int expected) {
assertEquals(expected, LongestIncreasingSubsequenceNLogN.lengthOfLIS(input));
}
}