Skip to content

Commit f8207cc

Browse files
fix(searches): support duplicates and null safety in LowerBound and UpperBound (#7648)
* 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. * 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. * 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. * docs(searches): document null return behavior, update best-case complexity, and test UpperBound duplicates
1 parent d9b86d1 commit f8207cc

4 files changed

Lines changed: 54 additions & 8 deletions

File tree

‎src/main/java/com/thealgorithms/searches/LowerBound.java‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* This is an extension of BinarySearch.
1414
*
1515
* <p>
16-
* Worst-case performance O(log n) Best-case performance O(1) Average
16+
* Worst-case performance O(log n) Best-case performance O(log n) Average
1717
* performance O(log n) Worst-case space complexity O(1)
1818
*
1919
* @author Pratik Padalia (https://github.com/15pratik)
@@ -26,10 +26,13 @@ class LowerBound implements SearchAlgorithm {
2626
* @param array is an array where the LowerBound value is to be found
2727
* @param key is an element for which the LowerBound is to be found
2828
* @param <T> is any comparable type
29-
* @return index of the LowerBound element
29+
* @return index of the first element not less than key, or 0 if array is null/empty or key is null
3030
*/
3131
@Override
3232
public <T extends Comparable<T>> int find(T[] array, T key) {
33+
if (array == null || array.length == 0 || key == null) {
34+
return 0;
35+
}
3336
return search(array, key, 0, array.length - 1);
3437
}
3538

@@ -51,10 +54,8 @@ private <T extends Comparable<T>> int search(T[] array, T key, int left, int rig
5154
int median = (left + right) >>> 1;
5255
int comp = key.compareTo(array[median]);
5356

54-
if (comp == 0) {
55-
return median;
56-
} else if (comp < 0) {
57-
// median position can be a possible solution
57+
if (comp <= 0) {
58+
// key is less than or equal to array[median], median position can be a possible solution
5859
return search(array, key, left, median);
5960
} else {
6061
// key we are looking is greater, so we must look on the right of median position

‎src/main/java/com/thealgorithms/searches/UpperBound.java‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* This is an extension of BinarySearch.
1414
*
1515
* <p>
16-
* Worst-case performance O(log n) Best-case performance O(1) Average
16+
* Worst-case performance O(log n) Best-case performance O(log n) Average
1717
* performance O(log n) Worst-case space complexity O(1)
1818
*
1919
* @author Pratik Padalia (https://github.com/15pratik)
@@ -26,10 +26,13 @@ class UpperBound implements SearchAlgorithm {
2626
* @param array is an array where the UpperBound value is to be found
2727
* @param key is an element for which the UpperBound is to be found
2828
* @param <T> is any comparable type
29-
* @return index of the UpperBound element
29+
* @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
3030
*/
3131
@Override
3232
public <T extends Comparable<T>> int find(T[] array, T key) {
33+
if (array == null || array.length == 0 || key == null) {
34+
return 0;
35+
}
3336
return search(array, key, 0, array.length - 1);
3437
}
3538

‎src/test/java/com/thealgorithms/searches/LowerBoundTest.java‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,31 @@ void testLowerBoundNonExistentValue() {
5656
assertEquals(4, lowerBound.find(array, 7), "Lower bound for 7 should be at index 4");
5757
assertEquals(0, lowerBound.find(array, 0), "Lower bound for 0 should be at index 0");
5858
}
59+
60+
/**
61+
* Test finding the lower bound for an element with duplicate occurrences.
62+
* Lower bound should point to the FIRST element not less than key.
63+
*/
64+
@Test
65+
void testLowerBoundDuplicates() {
66+
LowerBound lowerBound = new LowerBound();
67+
Integer[] arrayWithDuplicates = {1, 2, 2, 2, 3, 4, 5};
68+
69+
assertEquals(1, lowerBound.find(arrayWithDuplicates, 2), "Lower bound for 2 should be at index 1 (first occurrence)");
70+
71+
Integer[] allSame = {2, 2, 2, 2, 2};
72+
assertEquals(0, lowerBound.find(allSame, 2), "Lower bound for 2 in all-same array should be at index 0");
73+
}
74+
75+
/**
76+
* Test edge cases: empty array, null array, and null key.
77+
*/
78+
@Test
79+
void testLowerBoundNullAndEmptyEdgeCases() {
80+
LowerBound lowerBound = new LowerBound();
81+
Integer[] emptyArray = {};
82+
assertEquals(0, lowerBound.find(emptyArray, 5), "Lower bound for an empty array should be 0");
83+
assertEquals(0, lowerBound.find(null, 5), "Lower bound for a null array should be 0");
84+
assertEquals(0, lowerBound.find(new Integer[] {1, 2, 3}, null), "Lower bound for a null key should be 0");
85+
}
5986
}

‎src/test/java/com/thealgorithms/searches/UpperBoundTest.java‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ void testUpperBoundMultipleValues() {
5757
assertTrue(arrayWithDuplicates[index] > key, "Element at the upper bound index should be greater than the key.");
5858
}
5959

60+
@Test
61+
void testUpperBoundDuplicates() {
62+
Integer[] arrayWithDuplicates = {1, 2, 2, 2, 3};
63+
assertEquals(4, upperBound.find(arrayWithDuplicates, 2), "Upper bound for 2 in {1, 2, 2, 2, 3} should be at index 4");
64+
65+
Integer[] allSame = {2, 2, 2, 2, 2};
66+
assertEquals(4, upperBound.find(allSame, 2), "Upper bound for 2 in all-same array should be at last index 4");
67+
}
68+
6069
@Test
6170
void testUpperBoundLowerThanMin() {
6271
int key = 0; // Test with a key lower than the minimum element
@@ -96,4 +105,10 @@ void testUpperBoundSingleElementArray() {
96105
index = upperBound.find(singleElementArray, 15);
97106
assertEquals(0, index, "Upper bound for 15 in a single element array should be 0.");
98107
}
108+
109+
@Test
110+
void testUpperBoundNullArrayAndKey() {
111+
assertEquals(0, upperBound.find(null, 5), "Upper bound for a null array should be 0.");
112+
assertEquals(0, upperBound.find(new Integer[] {1, 2, 3}, null), "Upper bound for a null key should be 0.");
113+
}
99114
}

0 commit comments

Comments
 (0)