This commit is contained in:
krahets
2025-12-31 19:37:45 +08:00
parent 29ec0c699d
commit 3c9d5689c4
279 changed files with 40895 additions and 16087 deletions
+271 -64
View File
@@ -2,22 +2,22 @@
comments: true
---
# 10.3   Binary search boundaries
# 10.3   Binary Search Edge Cases
## 10.3.1   Find the left boundary
## 10.3.1   Finding the Left Boundary
!!! question
Given a sorted array `nums` of length $n$, which may contain duplicate elements, return the index of the leftmost element `target`. If the element is not present in the array, return $-1$.
Given a sorted array `nums` of length $n$ that may contain duplicate elements, return the index of the leftmost element `target` in the array. If the array does not contain the element, return $-1$.
Recalling the method of binary search for an insertion point, after the search is completed, the index $i$ will point to the leftmost occurrence of `target`. Therefore, **searching for the insertion point is essentially the same as finding the index of the leftmost `target`**.
Recall the method for finding the insertion point with binary search. After the search completes, $i$ points to the leftmost `target`, **so finding the insertion point is essentially finding the index of the leftmost `target`**.
We can use the function for finding an insertion point to find the left boundary of `target`. Note that the array might not contain `target`, which could lead to the following two results:
Consider implementing the left boundary search using the insertion point finding function. Note that the array may not contain `target`, which could result in the following two cases:
- The index $i$ of the insertion point is out of bounds.
- The insertion point index $i$ is out of bounds.
- The element `nums[i]` is not equal to `target`.
In these cases, simply return $-1$. The code is as follows:
When either of these situations occurs, simply return $-1$. The code is shown below:
=== "Python"
@@ -26,7 +26,7 @@ In these cases, simply return $-1$. The code is as follows:
"""Binary search for the leftmost target"""
# Equivalent to finding the insertion point of target
i = binary_search_insertion(nums, target)
# Did not find target, thus return -1
# Target not found, return -1
if i == len(nums) or nums[i] != target:
return -1
# Found target, return index i
@@ -40,7 +40,7 @@ In these cases, simply return $-1$. The code is as follows:
int binarySearchLeftEdge(vector<int> &nums, int target) {
// Equivalent to finding the insertion point of target
int i = binarySearchInsertion(nums, target);
// Did not find target, thus return -1
// Target not found, return -1
if (i == nums.size() || nums[i] != target) {
return -1;
}
@@ -56,7 +56,7 @@ In these cases, simply return $-1$. The code is as follows:
int binarySearchLeftEdge(int[] nums, int target) {
// Equivalent to finding the insertion point of target
int i = binary_search_insertion.binarySearchInsertion(nums, target);
// Did not find target, thus return -1
// Target not found, return -1
if (i == nums.length || nums[i] != target) {
return -1;
}
@@ -68,86 +68,179 @@ In these cases, simply return $-1$. The code is as follows:
=== "C#"
```csharp title="binary_search_edge.cs"
[class]{binary_search_edge}-[func]{BinarySearchLeftEdge}
/* Binary search for the leftmost target */
int BinarySearchLeftEdge(int[] nums, int target) {
// Equivalent to finding the insertion point of target
int i = binary_search_insertion.BinarySearchInsertion(nums, target);
// Target not found, return -1
if (i == nums.Length || nums[i] != target) {
return -1;
}
// Found target, return index i
return i;
}
```
=== "Go"
```go title="binary_search_edge.go"
[class]{}-[func]{binarySearchLeftEdge}
/* Binary search for the leftmost target */
func binarySearchLeftEdge(nums []int, target int) int {
// Equivalent to finding the insertion point of target
i := binarySearchInsertion(nums, target)
// Target not found, return -1
if i == len(nums) || nums[i] != target {
return -1
}
// Found target, return index i
return i
}
```
=== "Swift"
```swift title="binary_search_edge.swift"
[class]{}-[func]{binarySearchLeftEdge}
/* Binary search for the leftmost target */
func binarySearchLeftEdge(nums: [Int], target: Int) -> Int {
// Equivalent to finding the insertion point of target
let i = binarySearchInsertion(nums: nums, target: target)
// Target not found, return -1
if i == nums.endIndex || nums[i] != target {
return -1
}
// Found target, return index i
return i
}
```
=== "JS"
```javascript title="binary_search_edge.js"
[class]{}-[func]{binarySearchLeftEdge}
/* Binary search for the leftmost target */
function binarySearchLeftEdge(nums, target) {
// Equivalent to finding the insertion point of target
const i = binarySearchInsertion(nums, target);
// Target not found, return -1
if (i === nums.length || nums[i] !== target) {
return -1;
}
// Found target, return index i
return i;
}
```
=== "TS"
```typescript title="binary_search_edge.ts"
[class]{}-[func]{binarySearchLeftEdge}
/* Binary search for the leftmost target */
function binarySearchLeftEdge(nums: Array<number>, target: number): number {
// Equivalent to finding the insertion point of target
const i = binarySearchInsertion(nums, target);
// Target not found, return -1
if (i === nums.length || nums[i] !== target) {
return -1;
}
// Found target, return index i
return i;
}
```
=== "Dart"
```dart title="binary_search_edge.dart"
[class]{}-[func]{binarySearchLeftEdge}
/* Binary search for the leftmost target */
int binarySearchLeftEdge(List<int> nums, int target) {
// Equivalent to finding the insertion point of target
int i = binarySearchInsertion(nums, target);
// Target not found, return -1
if (i == nums.length || nums[i] != target) {
return -1;
}
// Found target, return index i
return i;
}
```
=== "Rust"
```rust title="binary_search_edge.rs"
[class]{}-[func]{binary_search_left_edge}
/* Binary search for the leftmost target */
fn binary_search_left_edge(nums: &[i32], target: i32) -> i32 {
// Equivalent to finding the insertion point of target
let i = binary_search_insertion(nums, target);
// Target not found, return -1
if i == nums.len() as i32 || nums[i as usize] != target {
return -1;
}
// Found target, return index i
i
}
```
=== "C"
```c title="binary_search_edge.c"
[class]{}-[func]{binarySearchLeftEdge}
/* Binary search for the leftmost target */
int binarySearchLeftEdge(int *nums, int numSize, int target) {
// Equivalent to finding the insertion point of target
int i = binarySearchInsertion(nums, numSize, target);
// Target not found, return -1
if (i == numSize || nums[i] != target) {
return -1;
}
// Found target, return index i
return i;
}
```
=== "Kotlin"
```kotlin title="binary_search_edge.kt"
[class]{}-[func]{binarySearchLeftEdge}
/* Binary search for the leftmost target */
fun binarySearchLeftEdge(nums: IntArray, target: Int): Int {
// Equivalent to finding the insertion point of target
val i = binarySearchInsertion(nums, target)
// Target not found, return -1
if (i == nums.size || nums[i] != target) {
return -1
}
// Found target, return index i
return i
}
```
=== "Ruby"
```ruby title="binary_search_edge.rb"
[class]{}-[func]{binary_search_left_edge}
### Binary search leftmost target ###
def binary_search_left_edge(nums, target)
# Equivalent to finding the insertion point of target
i = binary_search_insertion(nums, target)
# Target not found, return -1
return -1 if i == nums.length || nums[i] != target
i # Found target, return index i
end
```
=== "Zig"
## 10.3.2 &nbsp; Finding the Right Boundary
```zig title="binary_search_edge.zig"
[class]{}-[func]{binarySearchLeftEdge}
```
So how do we find the rightmost `target`? The most direct approach is to modify the code and replace the pointer shrinking operation in the `nums[m] == target` case. The code is omitted here; interested readers can implement it themselves.
## 10.3.2 &nbsp; Find the right boundary
Below we introduce two more clever methods.
How do we find the rightmost occurrence of `target`? The most straightforward way is to modify the traditional binary search logic by changing how we adjust the search boundaries in the case of `nums[m] == target`. The code is omitted here. If you are interested, try to implement the code on your own.
### 1. &nbsp; Reusing Left Boundary Search
Below we are going to introduce two more ingenious methods.
In fact, we can use the function for finding the leftmost element to find the rightmost element. The specific method is: **Convert finding the rightmost `target` into finding the leftmost `target + 1`**.
### 1. &nbsp; Reuse the left boundary search
As shown in Figure 10-7, after the search completes, pointer $i$ points to the leftmost `target + 1` (if it exists), while $j$ points to the rightmost `target`, **so we can simply return $j$**.
To find the rightmost occurrence of `target`, we can reuse the function used for locating the leftmost `target`. Specifically, we transform the search for the rightmost target into a search for the leftmost target + 1.
![Converting right boundary search to left boundary search](binary_search_edge.assets/binary_search_right_edge_by_left_edge.png){ class="animation-figure" }
As shown in Figure 10-7, after the search is complete, pointer $i$ will point to the leftmost `target + 1` (if exists), while pointer $j$ will point to the rightmost occurrence of `target`. Therefore, returning $j$ will give us the right boundary.
<p align="center"> Figure 10-7 &nbsp; Converting right boundary search to left boundary search </p>
![Transforming the search for the right boundary into the search for the left boundary](binary_search_edge.assets/binary_search_right_edge_by_left_edge.png){ class="animation-figure" }
<p align="center"> Figure 10-7 &nbsp; Transforming the search for the right boundary into the search for the left boundary </p>
Note that the insertion point returned is $i$, therefore, it should be subtracted by $1$ to obtain $j$:
Note that the returned insertion point is $i$, so we need to subtract $1$ from it to obtain $j$:
=== "Python"
@@ -158,7 +251,7 @@ Note that the insertion point returned is $i$, therefore, it should be subtracte
i = binary_search_insertion(nums, target + 1)
# j points to the rightmost target, i points to the first element greater than target
j = i - 1
# Did not find target, thus return -1
# Target not found, return -1
if j == -1 or nums[j] != target:
return -1
# Found target, return index j
@@ -174,7 +267,7 @@ Note that the insertion point returned is $i$, therefore, it should be subtracte
int i = binarySearchInsertion(nums, target + 1);
// j points to the rightmost target, i points to the first element greater than target
int j = i - 1;
// Did not find target, thus return -1
// Target not found, return -1
if (j == -1 || nums[j] != target) {
return -1;
}
@@ -192,7 +285,7 @@ Note that the insertion point returned is $i$, therefore, it should be subtracte
int i = binary_search_insertion.binarySearchInsertion(nums, target + 1);
// j points to the rightmost target, i points to the first element greater than target
int j = i - 1;
// Did not find target, thus return -1
// Target not found, return -1
if (j == -1 || nums[j] != target) {
return -1;
}
@@ -204,83 +297,197 @@ Note that the insertion point returned is $i$, therefore, it should be subtracte
=== "C#"
```csharp title="binary_search_edge.cs"
[class]{binary_search_edge}-[func]{BinarySearchRightEdge}
/* Binary search for the rightmost target */
int BinarySearchRightEdge(int[] nums, int target) {
// Convert to finding the leftmost target + 1
int i = binary_search_insertion.BinarySearchInsertion(nums, target + 1);
// j points to the rightmost target, i points to the first element greater than target
int j = i - 1;
// Target not found, return -1
if (j == -1 || nums[j] != target) {
return -1;
}
// Found target, return index j
return j;
}
```
=== "Go"
```go title="binary_search_edge.go"
[class]{}-[func]{binarySearchRightEdge}
/* Binary search for the rightmost target */
func binarySearchRightEdge(nums []int, target int) int {
// Convert to finding the leftmost target + 1
i := binarySearchInsertion(nums, target+1)
// j points to the rightmost target, i points to the first element greater than target
j := i - 1
// Target not found, return -1
if j == -1 || nums[j] != target {
return -1
}
// Found target, return index j
return j
}
```
=== "Swift"
```swift title="binary_search_edge.swift"
[class]{}-[func]{binarySearchRightEdge}
/* Binary search for the rightmost target */
func binarySearchRightEdge(nums: [Int], target: Int) -> Int {
// Convert to finding the leftmost target + 1
let i = binarySearchInsertion(nums: nums, target: target + 1)
// j points to the rightmost target, i points to the first element greater than target
let j = i - 1
// Target not found, return -1
if j == -1 || nums[j] != target {
return -1
}
// Found target, return index j
return j
}
```
=== "JS"
```javascript title="binary_search_edge.js"
[class]{}-[func]{binarySearchRightEdge}
/* Binary search for the rightmost target */
function binarySearchRightEdge(nums, target) {
// Convert to finding the leftmost target + 1
const i = binarySearchInsertion(nums, target + 1);
// j points to the rightmost target, i points to the first element greater than target
const j = i - 1;
// Target not found, return -1
if (j === -1 || nums[j] !== target) {
return -1;
}
// Found target, return index j
return j;
}
```
=== "TS"
```typescript title="binary_search_edge.ts"
[class]{}-[func]{binarySearchRightEdge}
/* Binary search for the rightmost target */
function binarySearchRightEdge(nums: Array<number>, target: number): number {
// Convert to finding the leftmost target + 1
const i = binarySearchInsertion(nums, target + 1);
// j points to the rightmost target, i points to the first element greater than target
const j = i - 1;
// Target not found, return -1
if (j === -1 || nums[j] !== target) {
return -1;
}
// Found target, return index j
return j;
}
```
=== "Dart"
```dart title="binary_search_edge.dart"
[class]{}-[func]{binarySearchRightEdge}
/* Binary search for the rightmost target */
int binarySearchRightEdge(List<int> nums, int target) {
// Convert to finding the leftmost target + 1
int i = binarySearchInsertion(nums, target + 1);
// j points to the rightmost target, i points to the first element greater than target
int j = i - 1;
// Target not found, return -1
if (j == -1 || nums[j] != target) {
return -1;
}
// Found target, return index j
return j;
}
```
=== "Rust"
```rust title="binary_search_edge.rs"
[class]{}-[func]{binary_search_right_edge}
/* Binary search for the rightmost target */
fn binary_search_right_edge(nums: &[i32], target: i32) -> i32 {
// Convert to finding the leftmost target + 1
let i = binary_search_insertion(nums, target + 1);
// j points to the rightmost target, i points to the first element greater than target
let j = i - 1;
// Target not found, return -1
if j == -1 || nums[j as usize] != target {
return -1;
}
// Found target, return index j
j
}
```
=== "C"
```c title="binary_search_edge.c"
[class]{}-[func]{binarySearchRightEdge}
/* Binary search for the rightmost target */
int binarySearchRightEdge(int *nums, int numSize, int target) {
// Convert to finding the leftmost target + 1
int i = binarySearchInsertion(nums, numSize, target + 1);
// j points to the rightmost target, i points to the first element greater than target
int j = i - 1;
// Target not found, return -1
if (j == -1 || nums[j] != target) {
return -1;
}
// Found target, return index j
return j;
}
```
=== "Kotlin"
```kotlin title="binary_search_edge.kt"
[class]{}-[func]{binarySearchRightEdge}
/* Binary search for the rightmost target */
fun binarySearchRightEdge(nums: IntArray, target: Int): Int {
// Convert to finding the leftmost target + 1
val i = binarySearchInsertion(nums, target + 1)
// j points to the rightmost target, i points to the first element greater than target
val j = i - 1
// Target not found, return -1
if (j == -1 || nums[j] != target) {
return -1
}
// Found target, return index j
return j
}
```
=== "Ruby"
```ruby title="binary_search_edge.rb"
[class]{}-[func]{binary_search_right_edge}
### Binary search rightmost target ###
def binary_search_right_edge(nums, target)
# Convert to finding the leftmost target + 1
i = binary_search_insertion(nums, target + 1)
# j points to the rightmost target, i points to the first element greater than target
j = i - 1
# Target not found, return -1
return -1 if j == -1 || nums[j] != target
j # Found target, return index j
end
```
=== "Zig"
### 2. &nbsp; Converting to Element Search
```zig title="binary_search_edge.zig"
[class]{}-[func]{binarySearchRightEdge}
```
We know that when the array does not contain `target`, $i$ and $j$ will eventually point to the first elements greater than and less than `target`, respectively.
### 2. &nbsp; Transform into an element search
Therefore, as shown in Figure 10-8, we can construct an element that does not exist in the array to find the left and right boundaries.
When the array does not contain `target`, $i$ and $j$ will eventually point to the first element greater and smaller than `target` respectively.
- Finding the leftmost `target`: Can be converted to finding `target - 0.5` and returning pointer $i$.
- Finding the rightmost `target`: Can be converted to finding `target + 0.5` and returning pointer $j$.
Thus, as shown in Figure 10-8, we can construct an element that does not exist in the array, to search for the left and right boundaries.
![Converting boundary search to element search](binary_search_edge.assets/binary_search_edge_by_element.png){ class="animation-figure" }
- To find the leftmost `target`: it can be transformed into searching for `target - 0.5`, and return the pointer $i$.
- To find the rightmost `target`: it can be transformed into searching for `target + 0.5`, and return the pointer $j$.
<p align="center"> Figure 10-8 &nbsp; Converting boundary search to element search </p>
![Transforming the search for boundaries into the search for an element](binary_search_edge.assets/binary_search_edge_by_element.png){ class="animation-figure" }
The code is omitted here, but the following two points are worth noting:
<p align="center"> Figure 10-8 &nbsp; Transforming the search for boundaries into the search for an element </p>
The code is omitted here, but here are two important points to note about this approach.
- The given array `nums` does not contain decimal, so handling equal cases is not a concern.
- However, introducing decimals in this approach requires modifying the `target` variable to a floating-point type (no change needed in Python).
- Since the given array does not contain decimals, we don't need to worry about how to handle equal cases.
- Because this method introduces decimals, the variable `target` in the function needs to be changed to a floating-point type (Python does not require this change).