Skip to content

Commit 1843cb1

Browse files
Find First and Last Position of Element in Sorted Array - Leetcode 34
1 parent 9f5cb98 commit 1843cb1

File tree

3 files changed

+304
-0
lines changed

3 files changed

+304
-0
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package array.lower_bound_upper_bound;
2+
3+
/**
4+
* Implements a solution for LeetCode problem 34: "Find First and Last Position of
5+
* Element in Sorted Array".
6+
*
7+
* <p>This class uses two modified binary search methods, {@code lowerBound} and
8+
* {@code upperBound}, to find the starting and ending indices of a target value
9+
* in a sorted array.
10+
*
11+
* @see <a href="https://leetcode.com/problems/find-first-and-last-position-of-element-in-sorted-array/">34. Find First and Last Position of Element in Sorted Array</a>
12+
*/
13+
public class FirstLastPosition {
14+
15+
/**
16+
* Finds the starting and ending positions of a given target value in a sorted array.
17+
*
18+
* <p>This method orchestrates two binary searches: one to find the leftmost
19+
* occurrence (lower bound) and another to find the rightmost occurrence
20+
* (upper bound) of the target.
21+
*
22+
* @param nums A non-decreasingly sorted array of integers.
23+
* @param target The value to search for.
24+
* @return An array of two integers {@code [start, end]} representing the
25+
* first and last indices of the target. If the target is not found,
26+
* it returns {@code [-1, -1]}.
27+
*/
28+
public int[] searchRange(int[] nums, int target) {
29+
if (nums == null || nums.length == 0) {
30+
return new int[]{-1, -1};
31+
}
32+
int first = lowerBound(nums, target);
33+
// If lowerBound doesn't find the target, we can skip the second search.
34+
if (first == -1) {
35+
return new int[]{-1, -1};
36+
}
37+
int last = upperBound(nums, target);
38+
return new int[]{first, last};
39+
}
40+
41+
/**
42+
* Finds the leftmost index of a target value using binary search.
43+
*
44+
* <p>This search is modified to continue searching the left half of the array
45+
* even after finding a match, ensuring it locates the very first occurrence.
46+
*
47+
* @param nums The sorted array to search.
48+
* @param target The value to find.
49+
* @return The index of the first occurrence of the target, or -1 if not found.
50+
*/
51+
private int lowerBound(int[] nums, int target) {
52+
int low = 0;
53+
int high = nums.length - 1;
54+
int answer = -1;
55+
while (low <= high) {
56+
int mid = low + (high - low) / 2; // Prevents potential overflow
57+
if (nums[mid] > target) {
58+
high = mid - 1;
59+
} else if (nums[mid] == target) {
60+
answer = mid; // Found a potential answer, try to find an earlier one
61+
high = mid - 1;
62+
} else {
63+
low = mid + 1;
64+
}
65+
}
66+
return answer;
67+
}
68+
69+
/**
70+
* Finds the rightmost index of a target value using binary search.
71+
*
72+
* <p>This search is modified to continue searching the right half of the array
73+
* even after finding a match, ensuring it locates the very last occurrence.
74+
*
75+
* @param nums The sorted array to search.
76+
* @param target The value to find.
77+
* @return The index of the last occurrence of the target, or -1 if not found.
78+
*/
79+
private int upperBound(int[] nums, int target) {
80+
int low = 0;
81+
int high = nums.length - 1;
82+
int answer = -1;
83+
while (low <= high) {
84+
int mid = low + (high - low) / 2; // Prevents potential overflow
85+
if (nums[mid] < target) {
86+
low = mid + 1;
87+
} else if (nums[mid] == target) {
88+
answer = mid; // Found a potential answer, try to find a later one
89+
low = mid + 1;
90+
} else {
91+
high = mid - 1;
92+
}
93+
}
94+
return answer;
95+
}
96+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* Provides implementations and explanations for Lower Bound and Upper Bound algorithms.
3+
*
4+
* <h2>Lower Bound and Upper Bound in Java</h2>
5+
*
6+
* <p>Lower Bound and Upper Bound are concepts derived from binary search, commonly
7+
* used in algorithms involving sorted arrays. They help find elements or their
8+
* potential insertion positions efficiently. These concepts are well-known
9+
* from the C++ Standard Template Library (STL) functions {@code std::lower_bound}
10+
* and {@code std::upper_bound}.
11+
*
12+
* <h3>Lower Bound</h3>
13+
*
14+
* <p>The <b>lower bound</b> algorithm finds the index of the <em>first element</em> in a
15+
* sorted array that is <b>greater than or equal to</b> a given value {@code x}.
16+
* <ul>
17+
* <li>If {@code x} exists in the array, it returns the index of its first occurrence.</li>
18+
* <li>If {@code x} does not exist, it returns the index where {@code x} would be
19+
* inserted to maintain the sorted order.</li>
20+
* <li>If all elements in the array are less than {@code x}, it returns the length of the array.</li>
21+
* </ul>
22+
* For example, in the array {@code [10, 20, 30, 30, 40, 50]}:
23+
* <ul>
24+
* <li>The lower bound of {@code 30} is index 2.</li>
25+
* <li>The lower bound of {@code 35} (a non-existent element) is index 4.</li>
26+
* </ul>
27+
*
28+
* <h3>Upper Bound</h3>
29+
*
30+
* <p>The <b>upper bound</b> algorithm finds the index of the <em>first element</em> in a
31+
* sorted array that is <b>strictly greater than</b> a given value {@code x}.
32+
* <ul>
33+
* <li>It always returns an index pointing to an element that is larger than {@code x}.</li>
34+
* <li>If all elements are less than or equal to {@code x}, it returns the length of the array.</li>
35+
* </ul>
36+
* For example, in the array {@code [10, 20, 30, 30, 40, 50]}:
37+
* <ul>
38+
* <li>The upper bound of {@code 30} is index 4 (pointing to the element {@code 40}).</li>
39+
* <li>The upper bound of {@code 50} is index 6 (the length of the array).</li>
40+
* </ul>
41+
*
42+
* <h3>LeetCode Application: Find First and Last Position of Element</h3>
43+
*
44+
* <p>A direct application of these concepts is LeetCode problem 34.
45+
* <blockquote>
46+
* Given an array of integers {@code nums} sorted in non-decreasing order, find the starting and ending
47+
* position of a given {@code target} value. If {@code target} is not found in the array, return {@code [-1, -1]}.
48+
* You must write an algorithm with O(log n) runtime complexity.
49+
* </blockquote>
50+
* This problem can be solved efficiently by finding the lower and upper bounds of the target value.
51+
* <ul>
52+
* <li>The <b>starting position</b> is the {@code lower_bound(target)}.</li>
53+
* <li>The <b>ending position</b> is {@code upper_bound(target) - 1}.</li>
54+
* </ul>
55+
* If the lower bound index is out of bounds or the element at that index does not match the target,
56+
* then the target does not exist in the array, and {@code [-1, -1]} should be returned.
57+
*
58+
* <p>For more details, see the problem on LeetCode:
59+
* <a href="https://leetcode.com/problems/find-first-and-last-position-of-element-in-sorted-array/">34. Find First and Last Position of Element in Sorted Array</a>
60+
*
61+
* <h3>Availability in Java</h3>
62+
*
63+
* <p>While C++ provides these functions directly, Java's standard library offers
64+
* building blocks.
65+
*
66+
* <h4>{@code java.util.Arrays.binarySearch()}</h4>
67+
*
68+
* <p>The {@code Arrays.binarySearch()} method can be used to find the <b>lower bound</b> (also known as the insertion point).
69+
* <ul>
70+
* <li>If the key is found, it returns its index. Note that if there are duplicates, it does not guarantee which one it will find.</li>
71+
* <li>If the key is not found, it returns {@code (-(insertion point) - 1)}.</li>
72+
* </ul>
73+
* The insertion point can be calculated from a negative return value {@code i} as {@code -i - 1}.
74+
* To reliably find the lower bound for an element that might have duplicates, a custom binary search implementation is often clearer.
75+
*
76+
* <p>There is no direct equivalent for <b>upper bound</b> in the standard library, so it
77+
* typically requires a custom binary search implementation. This package provides
78+
* such implementations for clarity and direct use.
79+
*
80+
* @see java.util.Arrays#binarySearch(int[], int)
81+
*/
82+
package array.lower_bound_upper_bound;
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package array.lower_bound_upper_bound;
2+
3+
import org.junit.jupiter.api.BeforeEach;
4+
import org.junit.jupiter.api.DisplayName;
5+
import org.junit.jupiter.api.Test;
6+
7+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
8+
9+
/**
10+
* Test suite for the FirstLastPosition class.
11+
*/
12+
class FirstLastPositionTest {
13+
14+
private FirstLastPosition finder;
15+
16+
@BeforeEach
17+
void setUp() {
18+
finder = new FirstLastPosition();
19+
}
20+
21+
@Test
22+
@DisplayName("Test with target present multiple times in the middle")
23+
void testSearchRangeTargetInMiddle() {
24+
int[] nums = {5, 7, 7, 8, 8, 10};
25+
int target = 8;
26+
int[] expected = {3, 4};
27+
assertArrayEquals(expected, finder.searchRange(nums, target));
28+
}
29+
30+
@Test
31+
@DisplayName("Test with target not found in the array")
32+
void testSearchRangeTargetNotFound() {
33+
int[] nums = {5, 7, 7, 8, 8, 10};
34+
int target = 6;
35+
int[] expected = {-1, -1};
36+
assertArrayEquals(expected, finder.searchRange(nums, target));
37+
}
38+
39+
@Test
40+
@DisplayName("Test with an empty array")
41+
void testSearchRangeEmptyArray() {
42+
int[] nums = {};
43+
int target = 0;
44+
int[] expected = {-1, -1};
45+
assertArrayEquals(expected, finder.searchRange(nums, target));
46+
}
47+
48+
@Test
49+
@DisplayName("Test with a null array")
50+
void testSearchRangeNullArray() {
51+
int[] expected = {-1, -1};
52+
assertArrayEquals(expected, finder.searchRange(null, 5));
53+
}
54+
55+
@Test
56+
@DisplayName("Test with a single element array, target found")
57+
void testSearchRangeSingleElementFound() {
58+
int[] nums = {5};
59+
int target = 5;
60+
int[] expected = {0, 0};
61+
assertArrayEquals(expected, finder.searchRange(nums, target));
62+
}
63+
64+
@Test
65+
@DisplayName("Test with a single element array, target not found")
66+
void testSearchRangeSingleElementNotFound() {
67+
int[] nums = {5};
68+
int target = 3;
69+
int[] expected = {-1, -1};
70+
assertArrayEquals(expected, finder.searchRange(nums, target));
71+
}
72+
73+
@Test
74+
@DisplayName("Test with target at the beginning of the array")
75+
void testSearchRangeTargetAtStart() {
76+
int[] nums = {2, 2, 3, 4, 5, 6};
77+
int target = 2;
78+
int[] expected = {0, 1};
79+
assertArrayEquals(expected, finder.searchRange(nums, target));
80+
}
81+
82+
@Test
83+
@DisplayName("Test with target at the end of the array")
84+
void testSearchRangeTargetAtEnd() {
85+
int[] nums = {1, 2, 3, 4, 5, 5};
86+
int target = 5;
87+
int[] expected = {4, 5};
88+
assertArrayEquals(expected, finder.searchRange(nums, target));
89+
}
90+
91+
@Test
92+
@DisplayName("Test where all elements are the target")
93+
void testSearchRangeAllElementsAreTarget() {
94+
int[] nums = {7, 7, 7, 7, 7};
95+
int target = 7;
96+
int[] expected = {0, 4};
97+
assertArrayEquals(expected, finder.searchRange(nums, target));
98+
}
99+
100+
@Test
101+
@DisplayName("Test with target smaller than all elements")
102+
void testSearchRangeTargetIsTooSmall() {
103+
int[] nums = {10, 20, 30, 40};
104+
int target = 5;
105+
int[] expected = {-1, -1};
106+
assertArrayEquals(expected, finder.searchRange(nums, target));
107+
}
108+
109+
@Test
110+
@DisplayName("Test with target larger than all elements")
111+
void testSearchRangeTargetIsTooLarge() {
112+
int[] nums = {10, 20, 30, 40};
113+
int target = 50;
114+
int[] expected = {-1, -1};
115+
assertArrayEquals(expected, finder.searchRange(nums, target));
116+
}
117+
118+
@Test
119+
@DisplayName("Test with a single occurrence of the target")
120+
void testSearchRangeSingleOccurrence() {
121+
int[] nums = {1, 2, 3, 4, 5};
122+
int target = 3;
123+
int[] expected = {2, 2};
124+
assertArrayEquals(expected, finder.searchRange(nums, target));
125+
}
126+
}

0 commit comments

Comments
 (0)