From 9186af65af6a15a72faf5c1735b2ed0dbcdb7017 Mon Sep 17 00:00:00 2001 From: Vusal Huseynov Date: Mon, 14 Apr 2025 20:48:38 +0400 Subject: [PATCH 01/10] Add LongestIncreasingSubsequenceNLogN class and corresponding test class --- .../dynamicprogramming/LongestIncreasingSubsequenceNLogN.java | 4 ++++ .../LongestIncreasingSubsequenceNLogNTest.java | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java create mode 100644 src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java new file mode 100644 index 000000000000..dfbc2c7e4998 --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java @@ -0,0 +1,4 @@ +package com.thealgorithms.dynamicprogramming; + +public class LongestIncreasingSubsequenceNLogN { +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java new file mode 100644 index 000000000000..a417318c1609 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java @@ -0,0 +1,4 @@ +package com.thealgorithms.dynamicprogramming; + +public class LongestIncreasingSubsequenceNLogNTest { +} From 153e3e1db3e07a169e1f355de0d460ece4bc50b1 Mon Sep 17 00:00:00 2001 From: Vusal Huseynov Date: Mon, 14 Apr 2025 20:48:40 +0400 Subject: [PATCH 02/10] Add LongestIncreasingSubsequenceNLogN class and corresponding test class --- .../LongestIncreasingSubsequenceNLogN.java | 67 +++++++++++++++++++ ...LongestIncreasingSubsequenceNLogNTest.java | 28 +++++++- 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java index dfbc2c7e4998..74caccb371e6 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java @@ -1,4 +1,71 @@ 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 class LongestIncreasingSubsequenceNLogN { + + /** + * Finds the index of the smallest element in the array that is greater than + * or equal to the target using binary search. If no such element exists, + * returns the index where the target can be inserted to maintain sorted order. + * + * @param arr The array to search in (assumed to be sorted up to a certain point). + * @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 l = 0, r = arr.length; + + 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); + + // 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; + } } diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java index a417318c1609..826cf6c4ebed 100644 --- a/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java +++ b/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java @@ -1,4 +1,30 @@ 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 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)); + } +} \ No newline at end of file From cb7edc8fd37011c0f132b18afa599dfc8908453b Mon Sep 17 00:00:00 2001 From: Vusal Huseynov Date: Mon, 14 Apr 2025 21:01:00 +0400 Subject: [PATCH 03/10] Refactor lowerBound method to include size parameter for improved functionality --- .../LongestIncreasingSubsequenceNLogN.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java index 74caccb371e6..94c942f61ab8 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java @@ -8,17 +8,18 @@ */ public class LongestIncreasingSubsequenceNLogN { - /** - * Finds the index of the smallest element in the array that is greater than - * or equal to the target using binary search. If no such element exists, - * returns the index where the target can be inserted to maintain sorted order. - * - * @param arr The array to search in (assumed to be sorted up to a certain point). - * @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 l = 0, r = arr.length; + /** + * 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, r = size; while (l < r) { int mid = l + (r - l) / 2; @@ -54,7 +55,7 @@ public static int lengthOfLIS(int[] arr) { for (int x : arr) { // Find the position to replace or extend the subsequence - int index = lowerBound(tails, x); + int index = lowerBound(tails, x, size); // Update the tails array with the current element tails[index] = x; From c2ba81846fcd0c2bec620dbbea239ce422afa47b Mon Sep 17 00:00:00 2001 From: Vusal Huseynov Date: Mon, 14 Apr 2025 21:02:49 +0400 Subject: [PATCH 04/10] Format test cases in LongestIncreasingSubsequenceNLogNTest for improved readability --- .../LongestIncreasingSubsequenceNLogNTest.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java index 826cf6c4ebed..b217de4093b1 100644 --- a/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java +++ b/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java @@ -3,6 +3,7 @@ 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; @@ -11,14 +12,14 @@ public class LongestIncreasingSubsequenceNLogNTest { private static Stream 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) + 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) ); } From a974cb56ca49e4f65ceb9fa5e5ff0bbb6751d28d Mon Sep 17 00:00:00 2001 From: Vusal Huseynov Date: Mon, 14 Apr 2025 21:09:09 +0400 Subject: [PATCH 05/10] Refactor comments and import statements for consistency and clarity in LongestIncreasingSubsequenceNLogN and its test class --- .../LongestIncreasingSubsequenceNLogN.java | 26 +++++++++---------- ...LongestIncreasingSubsequenceNLogNTest.java | 4 +-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java index 94c942f61ab8..c68990279ad6 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java @@ -8,16 +8,16 @@ */ public class 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. - */ + /** + * 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, r = size; @@ -29,12 +29,12 @@ private static int lowerBound(int[] arr, int target, int size) { l = mid + 1; } else { // Move left if target is less than or equal to mid element - r = mid; + r = mid; } } // Return the index where the target can be inserted - return l; + return l; } /** @@ -66,7 +66,7 @@ public static int lengthOfLIS(int[] arr) { } } - // Return the length of the LIS + // Return the length of the LIS return size; } } diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java index b217de4093b1..036249952b34 100644 --- a/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java +++ b/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java @@ -1,9 +1,9 @@ package com.thealgorithms.dynamicprogramming; -import static org.junit.jupiter.api.Assertions.assertEquals; - import java.util.stream.Stream; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; From 9df9f0f7d34a1aa31d7156028a5948d2898f8f5d Mon Sep 17 00:00:00 2001 From: Vusal Huseynov Date: Tue, 15 Apr 2025 08:44:13 +0400 Subject: [PATCH 06/10] Add private constructor to LongestIncreasingSubsequenceNLogN class to prevent instantiation --- .../dynamicprogramming/LongestIncreasingSubsequenceNLogN.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java index c68990279ad6..4e758e293324 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java @@ -7,6 +7,8 @@ * @author Vusal Huseynov (https://github.com/huseynovvusal) */ public class LongestIncreasingSubsequenceNLogN { + private LongestIncreasingSubsequenceNLogN() { + } /** * Finds the index of the smallest element in the array that is greater than From ecbd1c7b8667a195f5bfb82b3f982be4d7b5dc58 Mon Sep 17 00:00:00 2001 From: Vusal Huseynov Date: Tue, 15 Apr 2025 08:50:58 +0400 Subject: [PATCH 07/10] Make LongestIncreasingSubsequenceNLogN class final to prevent subclassing --- .../dynamicprogramming/LongestIncreasingSubsequenceNLogN.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java index 4e758e293324..25e5ec35d313 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java @@ -6,7 +6,7 @@ * * @author Vusal Huseynov (https://github.com/huseynovvusal) */ -public class LongestIncreasingSubsequenceNLogN { +public final class LongestIncreasingSubsequenceNLogN { private LongestIncreasingSubsequenceNLogN() { } From 9f0f22f61c12c32010e138346919e24e58e1a146 Mon Sep 17 00:00:00 2001 From: Vusal Huseynov Date: Tue, 15 Apr 2025 08:53:06 +0400 Subject: [PATCH 08/10] Refactor provideTestCases method for improved readability by consolidating return statement --- .../LongestIncreasingSubsequenceNLogNTest.java | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java index 036249952b34..2a4d1d508a24 100644 --- a/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java +++ b/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java @@ -11,16 +11,7 @@ public class LongestIncreasingSubsequenceNLogNTest { private static Stream 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) - ); + 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 From 059d7e52ee83e52045f5d4ede6e1cc9707f515b7 Mon Sep 17 00:00:00 2001 From: Vusal Huseynov Date: Tue, 15 Apr 2025 09:00:03 +0400 Subject: [PATCH 09/10] Fix formatting issues in LongestIncreasingSubsequenceNLogN class and its test class --- .../dynamicprogramming/LongestIncreasingSubsequenceNLogN.java | 3 ++- .../LongestIncreasingSubsequenceNLogNTest.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java index 25e5ec35d313..7bc0855e0566 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java @@ -21,7 +21,8 @@ private LongestIncreasingSubsequenceNLogN() { * @return The index of the lower bound. */ private static int lowerBound(int[] arr, int target, int size) { - int l = 0, r = size; + int l = 0; + int r = size; while (l < r) { int mid = l + (r - l) / 2; diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java index 2a4d1d508a24..69ea55b4d6a0 100644 --- a/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java +++ b/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java @@ -19,4 +19,4 @@ private static Stream provideTestCases() { public void testLengthOfLIS(int[] input, int expected) { assertEquals(expected, LongestIncreasingSubsequenceNLogN.lengthOfLIS(input)); } -} \ No newline at end of file +} From 04c70f8a2f724150b57e394ab767b809030d0cdb Mon Sep 17 00:00:00 2001 From: Vusal Huseynov Date: Tue, 15 Apr 2025 09:09:46 +0400 Subject: [PATCH 10/10] Refactor provideTestCases method for improved formatting and readability --- .../LongestIncreasingSubsequenceNLogNTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java index 69ea55b4d6a0..dc87d6751460 100644 --- a/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java +++ b/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java @@ -1,9 +1,8 @@ package com.thealgorithms.dynamicprogramming; -import java.util.stream.Stream; - 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; @@ -11,7 +10,8 @@ public class LongestIncreasingSubsequenceNLogNTest { private static Stream 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)); + 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