Skip to content

10.3   Binary search boundaries

10.3.1   Find 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\).

Recall the method of binary search for an insertion point, after the search is completed, \(i\) points to the leftmost target, thus searching for the insertion point is essentially searching for the index of the leftmost target.

Consider implementing the search for the left boundary using the function for finding an insertion point. Note that the array might not contain target, which could lead to the following two results:

  • The index \(i\) of the insertion point is out of bounds.
  • The element nums[i] is not equal to target.

In these cases, simply return \(-1\). The code is as follows:

binary_search_edge.py
def binary_search_left_edge(nums: list[int], target: int) -> int:
    """二分查找最左一个 target"""
    # 等价于查找 target 的插入点
    i = binary_search_insertion(nums, target)
    # 未找到 target ,返回 -1
    if i == len(nums) or nums[i] != target:
        return -1
    # 找到 target ,返回索引 i
    return i
binary_search_edge.cpp
/* 二分查找最左一个 target */
int binarySearchLeftEdge(vector<int> &nums, int target) {
    // 等价于查找 target 的插入点
    int i = binarySearchInsertion(nums, target);
    // 未找到 target ,返回 -1
    if (i == nums.size() || nums[i] != target) {
        return -1;
    }
    // 找到 target ,返回索引 i
    return i;
}
binary_search_edge.java
/* 二分查找最左一个 target */
int binarySearchLeftEdge(int[] nums, int target) {
    // 等价于查找 target 的插入点
    int i = binary_search_insertion.binarySearchInsertion(nums, target);
    // 未找到 target ,返回 -1
    if (i == nums.length || nums[i] != target) {
        return -1;
    }
    // 找到 target ,返回索引 i
    return i;
}
binary_search_edge.cs
/* 二分查找最左一个 target */
int BinarySearchLeftEdge(int[] nums, int target) {
    // 等价于查找 target 的插入点
    int i = binary_search_insertion.BinarySearchInsertion(nums, target);
    // 未找到 target ,返回 -1
    if (i == nums.Length || nums[i] != target) {
        return -1;
    }
    // 找到 target ,返回索引 i
    return i;
}
binary_search_edge.go
/* 二分查找最左一个 target */
func binarySearchLeftEdge(nums []int, target int) int {
    // 等价于查找 target 的插入点
    i := binarySearchInsertion(nums, target)
    // 未找到 target ,返回 -1
    if i == len(nums) || nums[i] != target {
        return -1
    }
    // 找到 target ,返回索引 i
    return i
}
binary_search_edge.swift
/* 二分查找最左一个 target */
func binarySearchLeftEdge(nums: [Int], target: Int) -> Int {
    // 等价于查找 target 的插入点
    let i = binarySearchInsertion(nums: nums, target: target)
    // 未找到 target ,返回 -1
    if i == nums.endIndex || nums[i] != target {
        return -1
    }
    // 找到 target ,返回索引 i
    return i
}
binary_search_edge.js
/* 二分查找最左一个 target */
function binarySearchLeftEdge(nums, target) {
    // 等价于查找 target 的插入点
    const i = binarySearchInsertion(nums, target);
    // 未找到 target ,返回 -1
    if (i === nums.length || nums[i] !== target) {
        return -1;
    }
    // 找到 target ,返回索引 i
    return i;
}
binary_search_edge.ts
/* 二分查找最左一个 target */
function binarySearchLeftEdge(nums: Array<number>, target: number): number {
    // 等价于查找 target 的插入点
    const i = binarySearchInsertion(nums, target);
    // 未找到 target ,返回 -1
    if (i === nums.length || nums[i] !== target) {
        return -1;
    }
    // 找到 target ,返回索引 i
    return i;
}
binary_search_edge.dart
/* 二分查找最左一个 target */
int binarySearchLeftEdge(List<int> nums, int target) {
  // 等价于查找 target 的插入点
  int i = binarySearchInsertion(nums, target);
  // 未找到 target ,返回 -1
  if (i == nums.length || nums[i] != target) {
    return -1;
  }
  // 找到 target ,返回索引 i
  return i;
}
binary_search_edge.rs
/* 二分查找最左一个 target */
fn binary_search_left_edge(nums: &[i32], target: i32) -> i32 {
    // 等价于查找 target 的插入点
    let i = binary_search_insertion(nums, target);
    // 未找到 target ,返回 -1
    if i == nums.len() as i32 || nums[i as usize] != target {
        return -1;
    }
    // 找到 target ,返回索引 i
    i
}
binary_search_edge.c
/* 二分查找最左一个 target */
int binarySearchLeftEdge(int *nums, int numSize, int target) {
    // 等价于查找 target 的插入点
    int i = binarySearchInsertion(nums, numSize, target);
    // 未找到 target ,返回 -1
    if (i == numSize || nums[i] != target) {
        return -1;
    }
    // 找到 target ,返回索引 i
    return i;
}
binary_search_edge.kt
/* 二分查找最左一个 target */
fun binarySearchLeftEdge(nums: IntArray, target: Int): Int {
    // 等价于查找 target 的插入点
    val i = binarySearchInsertion(nums, target)
    // 未找到 target ,返回 -1
    if (i == nums.size || nums[i] != target) {
        return -1
    }
    // 找到 target ,返回索引 i
    return i
}
binary_search_edge.rb
### 二分查找最左一个 target ###
def binary_search_left_edge(nums, target)
  # 等价于查找 target 的插入点
  i = binary_search_insertion(nums, target)

  # 未找到 target ,返回 -1
  return -1 if i == nums.length || nums[i] != target

  i # 找到 target ,返回索引 i
end
binary_search_edge.zig
[class]{}-[func]{binarySearchLeftEdge}
Code Visualization

10.3.2   Find the right boundary

So how do we find the rightmost target? The most straightforward way is to modify the code, replacing the pointer contraction operation in the case of nums[m] == target. The code is omitted here, but interested readers can implement it on their own.

Below we introduce two more cunning methods.

1.   Reusing the search for the left boundary

In fact, we can use the function for finding the leftmost element to find the rightmost element, specifically by transforming the search for the rightmost target into a search for the leftmost target + 1.

As shown in Figure 10-7, after the search is completed, the pointer \(i\) points to the leftmost target + 1 (if it exists), while \(j\) points to the rightmost target, thus returning \(j\) is sufficient.

Transforming the search for the right boundary into the search for the left boundary

Figure 10-7   Transforming the search for the right boundary into the search for the left boundary

Please note, the insertion point returned is \(i\), therefore, it should be subtracted by \(1\) to obtain \(j\):

binary_search_edge.py
def binary_search_right_edge(nums: list[int], target: int) -> int:
    """二分查找最右一个 target"""
    # 转化为查找最左一个 target + 1
    i = binary_search_insertion(nums, target + 1)
    # j 指向最右一个 target ,i 指向首个大于 target 的元素
    j = i - 1
    # 未找到 target ,返回 -1
    if j == -1 or nums[j] != target:
        return -1
    # 找到 target ,返回索引 j
    return j
binary_search_edge.cpp
/* 二分查找最右一个 target */
int binarySearchRightEdge(vector<int> &nums, int target) {
    // 转化为查找最左一个 target + 1
    int i = binarySearchInsertion(nums, target + 1);
    // j 指向最右一个 target ,i 指向首个大于 target 的元素
    int j = i - 1;
    // 未找到 target ,返回 -1
    if (j == -1 || nums[j] != target) {
        return -1;
    }
    // 找到 target ,返回索引 j
    return j;
}
binary_search_edge.java
/* 二分查找最右一个 target */
int binarySearchRightEdge(int[] nums, int target) {
    // 转化为查找最左一个 target + 1
    int i = binary_search_insertion.binarySearchInsertion(nums, target + 1);
    // j 指向最右一个 target ,i 指向首个大于 target 的元素
    int j = i - 1;
    // 未找到 target ,返回 -1
    if (j == -1 || nums[j] != target) {
        return -1;
    }
    // 找到 target ,返回索引 j
    return j;
}
binary_search_edge.cs
/* 二分查找最右一个 target */
int BinarySearchRightEdge(int[] nums, int target) {
    // 转化为查找最左一个 target + 1
    int i = binary_search_insertion.BinarySearchInsertion(nums, target + 1);
    // j 指向最右一个 target ,i 指向首个大于 target 的元素
    int j = i - 1;
    // 未找到 target ,返回 -1
    if (j == -1 || nums[j] != target) {
        return -1;
    }
    // 找到 target ,返回索引 j
    return j;
}
binary_search_edge.go
/* 二分查找最右一个 target */
func binarySearchRightEdge(nums []int, target int) int {
    // 转化为查找最左一个 target + 1
    i := binarySearchInsertion(nums, target+1)
    // j 指向最右一个 target ,i 指向首个大于 target 的元素
    j := i - 1
    // 未找到 target ,返回 -1
    if j == -1 || nums[j] != target {
        return -1
    }
    // 找到 target ,返回索引 j
    return j
}
binary_search_edge.swift
/* 二分查找最右一个 target */
func binarySearchRightEdge(nums: [Int], target: Int) -> Int {
    // 转化为查找最左一个 target + 1
    let i = binarySearchInsertion(nums: nums, target: target + 1)
    // j 指向最右一个 target ,i 指向首个大于 target 的元素
    let j = i - 1
    // 未找到 target ,返回 -1
    if j == -1 || nums[j] != target {
        return -1
    }
    // 找到 target ,返回索引 j
    return j
}
binary_search_edge.js
/* 二分查找最右一个 target */
function binarySearchRightEdge(nums, target) {
    // 转化为查找最左一个 target + 1
    const i = binarySearchInsertion(nums, target + 1);
    // j 指向最右一个 target ,i 指向首个大于 target 的元素
    const j = i - 1;
    // 未找到 target ,返回 -1
    if (j === -1 || nums[j] !== target) {
        return -1;
    }
    // 找到 target ,返回索引 j
    return j;
}
binary_search_edge.ts
/* 二分查找最右一个 target */
function binarySearchRightEdge(nums: Array<number>, target: number): number {
    // 转化为查找最左一个 target + 1
    const i = binarySearchInsertion(nums, target + 1);
    // j 指向最右一个 target ,i 指向首个大于 target 的元素
    const j = i - 1;
    // 未找到 target ,返回 -1
    if (j === -1 || nums[j] !== target) {
        return -1;
    }
    // 找到 target ,返回索引 j
    return j;
}
binary_search_edge.dart
/* 二分查找最右一个 target */
int binarySearchRightEdge(List<int> nums, int target) {
  // 转化为查找最左一个 target + 1
  int i = binarySearchInsertion(nums, target + 1);
  // j 指向最右一个 target ,i 指向首个大于 target 的元素
  int j = i - 1;
  // 未找到 target ,返回 -1
  if (j == -1 || nums[j] != target) {
    return -1;
  }
  // 找到 target ,返回索引 j
  return j;
}
binary_search_edge.rs
/* 二分查找最右一个 target */
fn binary_search_right_edge(nums: &[i32], target: i32) -> i32 {
    // 转化为查找最左一个 target + 1
    let i = binary_search_insertion(nums, target + 1);
    // j 指向最右一个 target ,i 指向首个大于 target 的元素
    let j = i - 1;
    // 未找到 target ,返回 -1
    if j == -1 || nums[j as usize] != target {
        return -1;
    }
    // 找到 target ,返回索引 j
    j
}
binary_search_edge.c
/* 二分查找最右一个 target */
int binarySearchRightEdge(int *nums, int numSize, int target) {
    // 转化为查找最左一个 target + 1
    int i = binarySearchInsertion(nums, numSize, target + 1);
    // j 指向最右一个 target ,i 指向首个大于 target 的元素
    int j = i - 1;
    // 未找到 target ,返回 -1
    if (j == -1 || nums[j] != target) {
        return -1;
    }
    // 找到 target ,返回索引 j
    return j;
}
binary_search_edge.kt
/* 二分查找最右一个 target */
fun binarySearchRightEdge(nums: IntArray, target: Int): Int {
    // 转化为查找最左一个 target + 1
    val i = binarySearchInsertion(nums, target + 1)
    // j 指向最右一个 target ,i 指向首个大于 target 的元素
    val j = i - 1
    // 未找到 target ,返回 -1
    if (j == -1 || nums[j] != target) {
        return -1
    }
    // 找到 target ,返回索引 j
    return j
}
binary_search_edge.rb
### 二分查找最右一个 target ###
def binary_search_right_edge(nums, target)
  # 转化为查找最左一个 target + 1
  i = binary_search_insertion(nums, target + 1)

  # j 指向最右一个 target ,i 指向首个大于 target 的元素
  j = i - 1

  # 未找到 target ,返回 -1
  return -1 if j == -1 || nums[j] != target

  j # 找到 target ,返回索引 j
end
binary_search_edge.zig
[class]{}-[func]{binarySearchRightEdge}
Code Visualization

We know that when the array does not contain target, \(i\) and \(j\) will eventually point to the first element greater and smaller than target respectively.

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.

  • 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\).

Transforming the search for boundaries into the search for an element

Figure 10-8   Transforming the search for boundaries into the search for an element

The code is omitted here, but two points are worth noting.

  • The given array does not contain decimals, meaning we do not need to worry about how to handle equal situations.
  • Since this method introduces decimals, the variable target in the function needs to be changed to a floating point type (no change needed in Python).
Feel free to drop your insights, questions or suggestions