From e7fbe44dd5ef1149d6759870322ac2fde8fb4dd5 Mon Sep 17 00:00:00 2001 From: Hardik Kaurani Date: Wed, 21 Oct 2026 17:06:21 +0530 Subject: [PATCH 1/4] fix(searches): find first occurrence in LowerBound with duplicate values When multiple elements equal to key exist, LowerBound should return the index of the first such value per its specification. Continue searching the left partition when comp <= 0 instead of prematurely returning the median element. --- src/main/java/com/thealgorithms/searches/LowerBound.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/thealgorithms/searches/LowerBound.java b/src/main/java/com/thealgorithms/searches/LowerBound.java index 5a1401edd3c2..edf8bdd89269 100644 --- a/src/main/java/com/thealgorithms/searches/LowerBound.java +++ b/src/main/java/com/thealgorithms/searches/LowerBound.java @@ -51,10 +51,8 @@ private > int search(T[] array, T key, int left, int rig int median = (left + right) >>> 1; int comp = key.compareTo(array[median]); - if (comp == 0) { - return median; - } else if (comp < 0) { - // median position can be a possible solution + if (comp <= 0) { + // key is less than or equal to array[median], median position can be a possible solution return search(array, key, left, median); } else { // key we are looking is greater, so we must look on the right of median position From 492efe55e292d5bdce8979ed91b44bf16fe5e58d Mon Sep 17 00:00:00 2001 From: Hardik Kaurani Date: Wed, 21 Oct 2026 17:08:17 +0530 Subject: [PATCH 2/4] fix(searches): add null and empty array safety to LowerBound and UpperBound Guard against NullPointerException and empty array bounds access in LowerBound and UpperBound find methods when array is null/empty or key is null. --- src/main/java/com/thealgorithms/searches/LowerBound.java | 3 +++ src/main/java/com/thealgorithms/searches/UpperBound.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/main/java/com/thealgorithms/searches/LowerBound.java b/src/main/java/com/thealgorithms/searches/LowerBound.java index edf8bdd89269..db14c383e172 100644 --- a/src/main/java/com/thealgorithms/searches/LowerBound.java +++ b/src/main/java/com/thealgorithms/searches/LowerBound.java @@ -30,6 +30,9 @@ class LowerBound implements SearchAlgorithm { */ @Override public > int find(T[] array, T key) { + if (array == null || array.length == 0 || key == null) { + return 0; + } return search(array, key, 0, array.length - 1); } diff --git a/src/main/java/com/thealgorithms/searches/UpperBound.java b/src/main/java/com/thealgorithms/searches/UpperBound.java index ec52c7a0ae5c..90e973da01f9 100644 --- a/src/main/java/com/thealgorithms/searches/UpperBound.java +++ b/src/main/java/com/thealgorithms/searches/UpperBound.java @@ -30,6 +30,9 @@ class UpperBound implements SearchAlgorithm { */ @Override public > int find(T[] array, T key) { + if (array == null || array.length == 0 || key == null) { + return 0; + } return search(array, key, 0, array.length - 1); } From 2f20fa84c6078e029b3d6f76040f09172a518d6d Mon Sep 17 00:00:00 2001 From: Hardik Kaurani Date: Wed, 21 Oct 2026 17:10:13 +0530 Subject: [PATCH 3/4] test(searches): add unit tests for LowerBound duplicate values and null safety Verify LowerBound returns the first occurrence for duplicate elements, and handles empty arrays, null arrays, and null keys gracefully in LowerBound and UpperBound. --- .../searches/LowerBoundTest.java | 27 +++++++++++++++++++ .../searches/UpperBoundTest.java | 6 +++++ 2 files changed, 33 insertions(+) diff --git a/src/test/java/com/thealgorithms/searches/LowerBoundTest.java b/src/test/java/com/thealgorithms/searches/LowerBoundTest.java index 30f4a5cb257c..058ea08cf614 100644 --- a/src/test/java/com/thealgorithms/searches/LowerBoundTest.java +++ b/src/test/java/com/thealgorithms/searches/LowerBoundTest.java @@ -56,4 +56,31 @@ void testLowerBoundNonExistentValue() { assertEquals(4, lowerBound.find(array, 7), "Lower bound for 7 should be at index 4"); assertEquals(0, lowerBound.find(array, 0), "Lower bound for 0 should be at index 0"); } + + /** + * Test finding the lower bound for an element with duplicate occurrences. + * Lower bound should point to the FIRST element not less than key. + */ + @Test + void testLowerBoundDuplicates() { + LowerBound lowerBound = new LowerBound(); + Integer[] arrayWithDuplicates = {1, 2, 2, 2, 3, 4, 5}; + + assertEquals(1, lowerBound.find(arrayWithDuplicates, 2), "Lower bound for 2 should be at index 1 (first occurrence)"); + + Integer[] allSame = {2, 2, 2, 2, 2}; + assertEquals(0, lowerBound.find(allSame, 2), "Lower bound for 2 in all-same array should be at index 0"); + } + + /** + * Test edge cases: empty array, null array, and null key. + */ + @Test + void testLowerBoundNullAndEmptyEdgeCases() { + LowerBound lowerBound = new LowerBound(); + Integer[] emptyArray = {}; + assertEquals(0, lowerBound.find(emptyArray, 5), "Lower bound for an empty array should be 0"); + assertEquals(0, lowerBound.find(null, 5), "Lower bound for a null array should be 0"); + assertEquals(0, lowerBound.find(new Integer[] {1, 2, 3}, null), "Lower bound for a null key should be 0"); + } } diff --git a/src/test/java/com/thealgorithms/searches/UpperBoundTest.java b/src/test/java/com/thealgorithms/searches/UpperBoundTest.java index dc0cbdd97e19..503d04e177e9 100644 --- a/src/test/java/com/thealgorithms/searches/UpperBoundTest.java +++ b/src/test/java/com/thealgorithms/searches/UpperBoundTest.java @@ -96,4 +96,10 @@ void testUpperBoundSingleElementArray() { index = upperBound.find(singleElementArray, 15); assertEquals(0, index, "Upper bound for 15 in a single element array should be 0."); } + + @Test + void testUpperBoundNullArrayAndKey() { + assertEquals(0, upperBound.find(null, 5), "Upper bound for a null array should be 0."); + assertEquals(0, upperBound.find(new Integer[] {1, 2, 3}, null), "Upper bound for a null key should be 0."); + } } From 4d50b7f9e6902ee3bb1626b99c70ba382a3d3295 Mon Sep 17 00:00:00 2001 From: Hardik Kaurani Date: Wed, 21 Oct 2026 20:56:16 +0530 Subject: [PATCH 4/4] docs(searches): document null return behavior, update best-case complexity, and test UpperBound duplicates --- src/main/java/com/thealgorithms/searches/LowerBound.java | 4 ++-- src/main/java/com/thealgorithms/searches/UpperBound.java | 4 ++-- .../java/com/thealgorithms/searches/UpperBoundTest.java | 9 +++++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/thealgorithms/searches/LowerBound.java b/src/main/java/com/thealgorithms/searches/LowerBound.java index db14c383e172..7325082c223b 100644 --- a/src/main/java/com/thealgorithms/searches/LowerBound.java +++ b/src/main/java/com/thealgorithms/searches/LowerBound.java @@ -13,7 +13,7 @@ * This is an extension of BinarySearch. * *

- * Worst-case performance O(log n) Best-case performance O(1) Average + * Worst-case performance O(log n) Best-case performance O(log n) Average * performance O(log n) Worst-case space complexity O(1) * * @author Pratik Padalia (https://github.com/15pratik) @@ -26,7 +26,7 @@ class LowerBound implements SearchAlgorithm { * @param array is an array where the LowerBound value is to be found * @param key is an element for which the LowerBound is to be found * @param is any comparable type - * @return index of the LowerBound element + * @return index of the first element not less than key, or 0 if array is null/empty or key is null */ @Override public > int find(T[] array, T key) { diff --git a/src/main/java/com/thealgorithms/searches/UpperBound.java b/src/main/java/com/thealgorithms/searches/UpperBound.java index 90e973da01f9..5344f22994b1 100644 --- a/src/main/java/com/thealgorithms/searches/UpperBound.java +++ b/src/main/java/com/thealgorithms/searches/UpperBound.java @@ -13,7 +13,7 @@ * This is an extension of BinarySearch. * *

- * Worst-case performance O(log n) Best-case performance O(1) Average + * Worst-case performance O(log n) Best-case performance O(log n) Average * performance O(log n) Worst-case space complexity O(1) * * @author Pratik Padalia (https://github.com/15pratik) @@ -26,7 +26,7 @@ class UpperBound implements SearchAlgorithm { * @param array is an array where the UpperBound value is to be found * @param key is an element for which the UpperBound is to be found * @param is any comparable type - * @return index of the UpperBound element + * @return index of the first element greater than key, last index if no such element exists, or 0 if array is null/empty or key is null */ @Override public > int find(T[] array, T key) { diff --git a/src/test/java/com/thealgorithms/searches/UpperBoundTest.java b/src/test/java/com/thealgorithms/searches/UpperBoundTest.java index 503d04e177e9..4f5eddbe924f 100644 --- a/src/test/java/com/thealgorithms/searches/UpperBoundTest.java +++ b/src/test/java/com/thealgorithms/searches/UpperBoundTest.java @@ -57,6 +57,15 @@ void testUpperBoundMultipleValues() { assertTrue(arrayWithDuplicates[index] > key, "Element at the upper bound index should be greater than the key."); } + @Test + void testUpperBoundDuplicates() { + Integer[] arrayWithDuplicates = {1, 2, 2, 2, 3}; + assertEquals(4, upperBound.find(arrayWithDuplicates, 2), "Upper bound for 2 in {1, 2, 2, 2, 3} should be at index 4"); + + Integer[] allSame = {2, 2, 2, 2, 2}; + assertEquals(4, upperBound.find(allSame, 2), "Upper bound for 2 in all-same array should be at last index 4"); + } + @Test void testUpperBoundLowerThanMin() { int key = 0; // Test with a key lower than the minimum element