From aad924da5421f9ba32a43a720566530a571124ac Mon Sep 17 00:00:00 2001 From: Ioana Cotutiu Date: Tue, 2 Mar 2021 14:40:15 +0200 Subject: [PATCH 1/6] add first draft for longest increasing subsequence Added implementation for the longest increasing subsequence. Co-Authored-By: Theodor Andrei Moise <38431187+iriediese@users.noreply.github.com> Co-Authored-By: Nihm <24206687+Nihm@users.noreply.github.com> Co-Authored-By: johennin <52281498+johennin@users.noreply.github.com> Co-Authored-By: JoaBon <34160048+JoaBon@users.noreply.github.com> --- .../dp/LongestIncreasingSubsequenceFast.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFast.java diff --git a/src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFast.java b/src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFast.java new file mode 100644 index 000000000..a5fb4be7f --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFast.java @@ -0,0 +1,63 @@ +package com.williamfiset.algorithms.dp; + +public class LongestIncreasingSubsequenceFast { + public static void main(String[] args) { + + System.out.println(lis(new int[] {1, 3, 2, 4, 3})); // 3 + System.out.println(lis(new int[] {2, 7, 4, 3, 8})); // 3 + System.out.println(lis(new int[] {5, 4, 3, 2, 1})); // 1 + System.out.println(lis(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9})); // 9 + System.out.println(lis(new int[] {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15})); // 6 + } + + public static int binarySearch(int[] ar, int[] c, int i, int sz) { + int mid, lo = 1, hi = sz; + while (lo <= hi) { + mid = (int) ((lo + hi) / 2); + if (c[mid] < ar[i]) { + lo = mid + 1; + } else { + hi = mid - 1; + } + } + //System.out.println("bs " + lo); + return lo; + } + + // Finds the length of the longest increasing subsequence length, O(n^2) + public static int lis(int[] ar) { + + if (ar == null || ar.length == 0) return 0; + int n = ar.length, len = 0, sz = 1; + + // When starting, each individual element has a LIS + // of exactly one, so each index is initialized to 1 + int[] dp = new int[n]; + int[] c = new int[n + 1]; + c[1] = ar[0]; + dp[0] = 1; + java.util.Arrays.fill(dp, 1); + java.util.Arrays.fill(c, ar[0]); + + // Processing the array left to right update the value of dp[j] if two + // conditions hold 1) The value at i is less than that of the one at j + // and 2) updating the value of dp[j] to dp[i]+1 is better + for (int i = 1; i < n; i++) { + if (ar[i] < c[1]) { + c[1] = ar[i]; /*you have to update the minimum value right now*/ + dp[i] = 1; + } else if (ar[i] > c[sz]) { + c[sz + 1] = ar[i]; + dp[i] = sz + 1; + sz++; + } else { + int k = binarySearch(ar, c, i, sz); /*you want to find k so that c[k-1] len) len = dp[i]; + } + + return len; + } +} From a5f57144dcba96aeaf492a908182401163713f41 Mon Sep 17 00:00:00 2001 From: Ioana Cotutiu Date: Wed, 3 Mar 2021 17:38:10 +0200 Subject: [PATCH 2/6] refactor longestIncreasingSubsequenceFast Added code documentation. Renamed variables. Refactored code. Co-Authored-By: Theodor Andrei Moise <38431187+iriediese@users.noreply.github.com> --- .../dp/LongestIncreasingSubsequenceFast.java | 98 ++++++++++--------- 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFast.java b/src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFast.java index a5fb4be7f..f89550ffe 100644 --- a/src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFast.java +++ b/src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFast.java @@ -1,63 +1,67 @@ package com.williamfiset.algorithms.dp; +/** + * Finds the longest increasing subsequence within an array of numbers + * obtained by removing the fewest possible elements from the initial array. + * + * Complexity: O(n * log n) + */ public class LongestIncreasingSubsequenceFast { public static void main(String[] args) { - System.out.println(lis(new int[] {1, 3, 2, 4, 3})); // 3 - System.out.println(lis(new int[] {2, 7, 4, 3, 8})); // 3 - System.out.println(lis(new int[] {5, 4, 3, 2, 1})); // 1 - System.out.println(lis(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9})); // 9 - System.out.println(lis(new int[] {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15})); // 6 + System.out.println(longestIncreasingSubsequenceLength(new int[] {1, 3, 2, 4, 3})); // 3 + System.out.println(longestIncreasingSubsequenceLength(new int[] {2, 7, 4, 3, 8})); // 3 + System.out.println(longestIncreasingSubsequenceLength(new int[] {5, 4, 3, 2, 1})); // 1 + System.out.println( + longestIncreasingSubsequenceLength(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9})); // 9 + System.out.println( + longestIncreasingSubsequenceLength( + new int[] {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15})); // 6 } - public static int binarySearch(int[] ar, int[] c, int i, int sz) { - int mid, lo = 1, hi = sz; - while (lo <= hi) { - mid = (int) ((lo + hi) / 2); - if (c[mid] < ar[i]) { - lo = mid + 1; - } else { - hi = mid - 1; + /** + * + * @param ar parsed array + * @param l lower bound representing the leftmost index of the array + * @param r upper bound representing the rightmost index of the array + * @param key value to be inserted + * @return index before which the key can be inserted + */ + static int binarySearch(int[] ar, int l, int r, int key) { + while (r - l > 1) { + int m = l + (r - l) / 2; + if (ar[m] >= key) { + r = m; + } else { + l = m; } } - //System.out.println("bs " + lo); - return lo; + return r; } - // Finds the length of the longest increasing subsequence length, O(n^2) - public static int lis(int[] ar) { - - if (ar == null || ar.length == 0) return 0; - int n = ar.length, len = 0, sz = 1; - - // When starting, each individual element has a LIS - // of exactly one, so each index is initialized to 1 - int[] dp = new int[n]; - int[] c = new int[n + 1]; - c[1] = ar[0]; - dp[0] = 1; - java.util.Arrays.fill(dp, 1); - java.util.Arrays.fill(c, ar[0]); - - // Processing the array left to right update the value of dp[j] if two - // conditions hold 1) The value at i is less than that of the one at j - // and 2) updating the value of dp[j] to dp[i]+1 is better - for (int i = 1; i < n; i++) { - if (ar[i] < c[1]) { - c[1] = ar[i]; /*you have to update the minimum value right now*/ - dp[i] = 1; - } else if (ar[i] > c[sz]) { - c[sz + 1] = ar[i]; - dp[i] = sz + 1; - sz++; - } else { - int k = binarySearch(ar, c, i, sz); /*you want to find k so that c[k-1] lis[len - 1]) { + lis[len++] = ar[i]; + } else { // position needs to be found for new value + lis[binarySearch(lis, -1, len - 1, ar[i])] = ar[i]; } - if (dp[i] > len) len = dp[i]; } - return len; } } From 1502a1038c9ac1d8908c22c95b41e023095573d1 Mon Sep 17 00:00:00 2001 From: Ioana Cotutiu Date: Wed, 3 Mar 2021 18:26:29 +0200 Subject: [PATCH 3/6] add tests for LongestIncreasingSubsequenceFast Added test cases for LongestIncreasingSubsequenceFast. Added some refactoring. Co-Authored-By: Theodor Andrei Moise <38431187+iriediese@users.noreply.github.com> --- .../dp/LongestIncreasingSubsequenceFast.java | 3 + .../LongestIncreasingSubsequenceFastTest.java | 58 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/test/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFastTest.java diff --git a/src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFast.java b/src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFast.java index f89550ffe..4a25f98a6 100644 --- a/src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFast.java +++ b/src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFast.java @@ -46,6 +46,9 @@ static int binarySearch(int[] ar, int l, int r, int key) { */ static int longestIncreasingSubsequenceLength(int[] ar) { int size = ar.length; + if (size == 0) { + return 0; + } int[] lis = new int[size]; int len; lis[0] = ar[0]; diff --git a/src/test/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFastTest.java b/src/test/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFastTest.java new file mode 100644 index 000000000..514538506 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFastTest.java @@ -0,0 +1,58 @@ +package com.williamfiset.algorithms.dp; + +import org.junit.Assert; +import org.junit.Test; + +// Checking that the returned values of the two different implementations are equal (consistent) +public class LongestIncreasingSubsequenceFastTest { + @Test + public void longestIncreasingSubsequenceFastEmpty(){ + int[] array = {}; + Assert.assertEquals(0, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); + Assert.assertEquals(LongestIncreasingSubsequence.lis(array), LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); + } + + @Test + public void longestIncreasingSubsequenceFastSingle(){ + int[] array = {3}; + Assert.assertEquals(1, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); + Assert.assertEquals(LongestIncreasingSubsequence.lis(array), LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); + } + + @Test + public void longestIncreasingSubsequenceFastRandom(){ + int[][] array = { + {1, 3, 2, 4, 3}, + {2, 7, 4, 3, 8}, + {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15} + }; + + Assert.assertEquals(3, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array[0])); + Assert.assertEquals(3, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array[1])); + Assert.assertEquals(6, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array[2])); + + for (int[] ints : array) { + Assert.assertEquals(LongestIncreasingSubsequence.lis(ints), LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(ints)); + } + } + + @Test + public void longestIncreasingSubsequenceFastAscending(){ + int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + Assert.assertEquals(9, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); + Assert.assertEquals(LongestIncreasingSubsequence.lis(array), LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); + } + + @Test + public void longestIncreasingSubsequenceFastDescending(){ + int[] array = {5, 4, 3, 2, 1}; + Assert.assertEquals(1, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); + Assert.assertEquals(LongestIncreasingSubsequence.lis(array), LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); + } + + @Test(expected = NullPointerException.class) + public void longestIncreasingSubsequenceFastNull(){ + LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(null); + LongestIncreasingSubsequence.lis(null); + } +} From eecb0a7e87635bc2062a8ddf8e9b8a89dd740d62 Mon Sep 17 00:00:00 2001 From: moise Date: Thu, 4 Mar 2021 10:55:53 +0100 Subject: [PATCH 4/6] fix spaces and javadocs according to the google java format --- .../LongestIncreasingSubsequenceFastTest.java | 110 +++++++++++------- 1 file changed, 65 insertions(+), 45 deletions(-) diff --git a/src/test/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFastTest.java b/src/test/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFastTest.java index 514538506..f63bb0dc9 100644 --- a/src/test/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFastTest.java +++ b/src/test/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFastTest.java @@ -3,56 +3,76 @@ import org.junit.Assert; import org.junit.Test; -// Checking that the returned values of the two different implementations are equal (consistent) +/** + * Checking that the returned values of the two different implementations are equal (consistent). + */ + public class LongestIncreasingSubsequenceFastTest { - @Test - public void longestIncreasingSubsequenceFastEmpty(){ - int[] array = {}; - Assert.assertEquals(0, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); - Assert.assertEquals(LongestIncreasingSubsequence.lis(array), LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); - } + @Test + public void longestIncreasingSubsequenceFastEmpty() { + int[] array = {}; + Assert.assertEquals( + 0, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); + Assert.assertEquals( + LongestIncreasingSubsequence.lis(array), + LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); + } - @Test - public void longestIncreasingSubsequenceFastSingle(){ - int[] array = {3}; - Assert.assertEquals(1, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); - Assert.assertEquals(LongestIncreasingSubsequence.lis(array), LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); - } + @Test + public void longestIncreasingSubsequenceFastSingle() { + int[] array = {3}; + Assert.assertEquals( + 1, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); + Assert.assertEquals( + LongestIncreasingSubsequence.lis(array), + LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); + } - @Test - public void longestIncreasingSubsequenceFastRandom(){ - int[][] array = { - {1, 3, 2, 4, 3}, - {2, 7, 4, 3, 8}, - {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15} - }; - - Assert.assertEquals(3, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array[0])); - Assert.assertEquals(3, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array[1])); - Assert.assertEquals(6, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array[2])); - - for (int[] ints : array) { - Assert.assertEquals(LongestIncreasingSubsequence.lis(ints), LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(ints)); - } - } + @Test + public void longestIncreasingSubsequenceFastRandom() { + int[][] array = { + {1, 3, 2, 4, 3}, + {2, 7, 4, 3, 8}, + {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15} + }; - @Test - public void longestIncreasingSubsequenceFastAscending(){ - int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9}; - Assert.assertEquals(9, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); - Assert.assertEquals(LongestIncreasingSubsequence.lis(array), LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); - } + Assert.assertEquals( + 3, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array[0])); + Assert.assertEquals( + 3, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array[1])); + Assert.assertEquals( + 6, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array[2])); - @Test - public void longestIncreasingSubsequenceFastDescending(){ - int[] array = {5, 4, 3, 2, 1}; - Assert.assertEquals(1, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); - Assert.assertEquals(LongestIncreasingSubsequence.lis(array), LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); + for (int[] ints : array) { + Assert.assertEquals( + LongestIncreasingSubsequence.lis(ints), + LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(ints)); } + } - @Test(expected = NullPointerException.class) - public void longestIncreasingSubsequenceFastNull(){ - LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(null); - LongestIncreasingSubsequence.lis(null); - } + @Test + public void longestIncreasingSubsequenceFastAscending() { + int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + Assert.assertEquals( + 9, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); + Assert.assertEquals( + LongestIncreasingSubsequence.lis(array), + LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); + } + + @Test + public void longestIncreasingSubsequenceFastDescending() { + int[] array = {5, 4, 3, 2, 1}; + Assert.assertEquals( + 1, LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); + Assert.assertEquals( + LongestIncreasingSubsequence.lis(array), + LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(array)); + } + + @Test(expected = NullPointerException.class) + public void longestIncreasingSubsequenceFastNull() { + LongestIncreasingSubsequenceFast.longestIncreasingSubsequenceLength(null); + LongestIncreasingSubsequence.lis(null); + } } From 04c7260a2086d78b3b16e47a7316450b42f13a86 Mon Sep 17 00:00:00 2001 From: moise Date: Thu, 4 Mar 2021 10:59:41 +0100 Subject: [PATCH 5/6] also add improved formatting to the implementation itself --- .../dp/LongestIncreasingSubsequenceFast.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFast.java b/src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFast.java index 4a25f98a6..439df92dd 100644 --- a/src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFast.java +++ b/src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFast.java @@ -1,26 +1,26 @@ package com.williamfiset.algorithms.dp; /** - * Finds the longest increasing subsequence within an array of numbers - * obtained by removing the fewest possible elements from the initial array. - * - * Complexity: O(n * log n) + * Finds the longest increasing subsequence within an array of numbers obtained by removing the + * fewest possible elements from the initial array. Complexity: O(n * log n) */ public class LongestIncreasingSubsequenceFast { + /** Main function. */ public static void main(String[] args) { System.out.println(longestIncreasingSubsequenceLength(new int[] {1, 3, 2, 4, 3})); // 3 System.out.println(longestIncreasingSubsequenceLength(new int[] {2, 7, 4, 3, 8})); // 3 System.out.println(longestIncreasingSubsequenceLength(new int[] {5, 4, 3, 2, 1})); // 1 System.out.println( - longestIncreasingSubsequenceLength(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9})); // 9 + longestIncreasingSubsequenceLength(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9})); // 9 System.out.println( - longestIncreasingSubsequenceLength( - new int[] {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15})); // 6 + longestIncreasingSubsequenceLength( + new int[] {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15})); // 6 } /** - * + * Function performing the binary search step of the main algorithm. + * * @param ar parsed array * @param l lower bound representing the leftmost index of the array * @param r upper bound representing the rightmost index of the array @@ -40,6 +40,7 @@ static int binarySearch(int[] ar, int l, int r, int key) { } /** + * Function computing the length of the longest increasing subsequence from an array. * * @param ar initial array * @return the length of the longest increasing subsequence @@ -57,9 +58,7 @@ static int longestIncreasingSubsequenceLength(int[] ar) { // minimum value needs updating if (ar[i] < lis[0]) { lis[0] = ar[i]; - } - // new value can be safely appended - else if (ar[i] > lis[len - 1]) { + } else if (ar[i] > lis[len - 1]) { // new value can be safely appended lis[len++] = ar[i]; } else { // position needs to be found for new value lis[binarySearch(lis, -1, len - 1, ar[i])] = ar[i]; From 164a360a0ce208815f87f222ec6b61f0a61563c2 Mon Sep 17 00:00:00 2001 From: moise Date: Thu, 4 Mar 2021 11:08:31 +0100 Subject: [PATCH 6/6] add one more formatting fix to the test file --- .../algorithms/dp/LongestIncreasingSubsequenceFastTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFastTest.java b/src/test/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFastTest.java index f63bb0dc9..824da251c 100644 --- a/src/test/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFastTest.java +++ b/src/test/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequenceFastTest.java @@ -6,7 +6,6 @@ /** * Checking that the returned values of the two different implementations are equal (consistent). */ - public class LongestIncreasingSubsequenceFastTest { @Test public void longestIncreasingSubsequenceFastEmpty() {