Skip to content

10.2   Binary search insertion

Binary search is not only used to search for target elements but also to solve many variant problems, such as searching for the insertion position of target elements.

10.2.1   Case with no duplicate elements

Question

Given an ordered array nums of length \(n\) and an element target, where the array has no duplicate elements. Now insert target into the array nums while maintaining its order. If the element target already exists in the array, insert it to its left side. Please return the index of target in the array after insertion. See the example shown in Figure 10-4.

Example data for binary search insertion point

Figure 10-4   Example data for binary search insertion point

If you want to reuse the binary search code from the previous section, you need to answer the following two questions.

Question one: When the array contains target, is the insertion point index the index of that element?

The requirement to insert target to the left of equal elements means that the newly inserted target replaces the original target position. Thus, when the array contains target, the insertion point index is the index of that target.

Question two: When the array does not contain target, what is the index of the insertion point?

Further consider the binary search process: when nums[m] < target, pointer \(i\) moves, meaning that pointer \(i\) is approaching an element greater than or equal to target. Similarly, pointer \(j\) is always approaching an element less than or equal to target.

Therefore, at the end of the binary, it is certain that: \(i\) points to the first element greater than target, and \(j\) points to the first element less than target. It is easy to see that when the array does not contain target, the insertion index is \(i\). The code is as follows:

binary_search_insertion.py
def binary_search_insertion_simple(nums: list[int], target: int) -> int:
    """二分查找插入点(无重复元素)"""
    i, j = 0, len(nums) - 1  # 初始化双闭区间 [0, n-1]
    while i <= j:
        m = (i + j) // 2  # 计算中点索引 m
        if nums[m] < target:
            i = m + 1  # target 在区间 [m+1, j] 中
        elif nums[m] > target:
            j = m - 1  # target 在区间 [i, m-1] 中
        else:
            return m  # 找到 target ,返回插入点 m
    # 未找到 target ,返回插入点 i
    return i
binary_search_insertion.cpp
/* 二分查找插入点(无重复元素) */
int binarySearchInsertionSimple(vector<int> &nums, int target) {
    int i = 0, j = nums.size() - 1; // 初始化双闭区间 [0, n-1]
    while (i <= j) {
        int m = i + (j - i) / 2; // 计算中点索引 m
        if (nums[m] < target) {
            i = m + 1; // target 在区间 [m+1, j] 中
        } else if (nums[m] > target) {
            j = m - 1; // target 在区间 [i, m-1] 中
        } else {
            return m; // 找到 target ,返回插入点 m
        }
    }
    // 未找到 target ,返回插入点 i
    return i;
}
binary_search_insertion.java
/* 二分查找插入点(无重复元素) */
int binarySearchInsertionSimple(int[] nums, int target) {
    int i = 0, j = nums.length - 1; // 初始化双闭区间 [0, n-1]
    while (i <= j) {
        int m = i + (j - i) / 2; // 计算中点索引 m
        if (nums[m] < target) {
            i = m + 1; // target 在区间 [m+1, j] 中
        } else if (nums[m] > target) {
            j = m - 1; // target 在区间 [i, m-1] 中
        } else {
            return m; // 找到 target ,返回插入点 m
        }
    }
    // 未找到 target ,返回插入点 i
    return i;
}
binary_search_insertion.cs
/* 二分查找插入点(无重复元素) */
int BinarySearchInsertionSimple(int[] nums, int target) {
    int i = 0, j = nums.Length - 1; // 初始化双闭区间 [0, n-1]
    while (i <= j) {
        int m = i + (j - i) / 2; // 计算中点索引 m
        if (nums[m] < target) {
            i = m + 1; // target 在区间 [m+1, j] 中
        } else if (nums[m] > target) {
            j = m - 1; // target 在区间 [i, m-1] 中
        } else {
            return m; // 找到 target ,返回插入点 m
        }
    }
    // 未找到 target ,返回插入点 i
    return i;
}
binary_search_insertion.go
/* 二分查找插入点(无重复元素) */
func binarySearchInsertionSimple(nums []int, target int) int {
    // 初始化双闭区间 [0, n-1]
    i, j := 0, len(nums)-1
    for i <= j {
        // 计算中点索引 m
        m := i + (j-i)/2
        if nums[m] < target {
            // target 在区间 [m+1, j] 中
            i = m + 1
        } else if nums[m] > target {
            // target 在区间 [i, m-1] 中
            j = m - 1
        } else {
            // 找到 target ,返回插入点 m
            return m
        }
    }
    // 未找到 target ,返回插入点 i
    return i
}
binary_search_insertion.swift
/* 二分查找插入点(无重复元素) */
func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int {
    // 初始化双闭区间 [0, n-1]
    var i = nums.startIndex
    var j = nums.endIndex - 1
    while i <= j {
        let m = i + (j - i) / 2 // 计算中点索引 m
        if nums[m] < target {
            i = m + 1 // target 在区间 [m+1, j] 中
        } else if nums[m] > target {
            j = m - 1 // target 在区间 [i, m-1] 中
        } else {
            return m // 找到 target ,返回插入点 m
        }
    }
    // 未找到 target ,返回插入点 i
    return i
}
binary_search_insertion.js
/* 二分查找插入点(无重复元素) */
function binarySearchInsertionSimple(nums, target) {
    let i = 0,
        j = nums.length - 1; // 初始化双闭区间 [0, n-1]
    while (i <= j) {
        const m = Math.floor(i + (j - i) / 2); // 计算中点索引 m, 使用 Math.floor() 向下取整
        if (nums[m] < target) {
            i = m + 1; // target 在区间 [m+1, j] 中
        } else if (nums[m] > target) {
            j = m - 1; // target 在区间 [i, m-1] 中
        } else {
            return m; // 找到 target ,返回插入点 m
        }
    }
    // 未找到 target ,返回插入点 i
    return i;
}
binary_search_insertion.ts
/* 二分查找插入点(无重复元素) */
function binarySearchInsertionSimple(
    nums: Array<number>,
    target: number
): number {
    let i = 0,
        j = nums.length - 1; // 初始化双闭区间 [0, n-1]
    while (i <= j) {
        const m = Math.floor(i + (j - i) / 2); // 计算中点索引 m, 使用 Math.floor() 向下取整
        if (nums[m] < target) {
            i = m + 1; // target 在区间 [m+1, j] 中
        } else if (nums[m] > target) {
            j = m - 1; // target 在区间 [i, m-1] 中
        } else {
            return m; // 找到 target ,返回插入点 m
        }
    }
    // 未找到 target ,返回插入点 i
    return i;
}
binary_search_insertion.dart
/* 二分查找插入点(无重复元素) */
int binarySearchInsertionSimple(List<int> nums, int target) {
  int i = 0, j = nums.length - 1; // 初始化双闭区间 [0, n-1]
  while (i <= j) {
    int m = i + (j - i) ~/ 2; // 计算中点索引 m
    if (nums[m] < target) {
      i = m + 1; // target 在区间 [m+1, j] 中
    } else if (nums[m] > target) {
      j = m - 1; // target 在区间 [i, m-1] 中
    } else {
      return m; // 找到 target ,返回插入点 m
    }
  }
  // 未找到 target ,返回插入点 i
  return i;
}
binary_search_insertion.rs
/* 二分查找插入点(无重复元素) */
fn binary_search_insertion_simple(nums: &[i32], target: i32) -> i32 {
    let (mut i, mut j) = (0, nums.len() as i32 - 1); // 初始化双闭区间 [0, n-1]
    while i <= j {
        let m = i + (j - i) / 2; // 计算中点索引 m
        if nums[m as usize] < target {
            i = m + 1; // target 在区间 [m+1, j] 中
        } else if nums[m as usize] > target {
            j = m - 1; // target 在区间 [i, m-1] 中
        } else {
            return m;
        }
    }
    // 未找到 target ,返回插入点 i
    i
}
binary_search_insertion.c
/* 二分查找插入点(无重复元素) */
int binarySearchInsertionSimple(int *nums, int numSize, int target) {
    int i = 0, j = numSize - 1; // 初始化双闭区间 [0, n-1]
    while (i <= j) {
        int m = i + (j - i) / 2; // 计算中点索引 m
        if (nums[m] < target) {
            i = m + 1; // target 在区间 [m+1, j] 中
        } else if (nums[m] > target) {
            j = m - 1; // target 在区间 [i, m-1] 中
        } else {
            return m; // 找到 target ,返回插入点 m
        }
    }
    // 未找到 target ,返回插入点 i
    return i;
}
binary_search_insertion.kt
/* 二分查找插入点(无重复元素) */
fun binarySearchInsertionSimple(nums: IntArray, target: Int): Int {
    var i = 0
    var j = nums.size - 1 // 初始化双闭区间 [0, n-1]
    while (i <= j) {
        val m = i + (j - i) / 2 // 计算中点索引 m
        if (nums[m] < target) {
            i = m + 1 // target 在区间 [m+1, j] 中
        } else if (nums[m] > target) {
            j = m - 1 // target 在区间 [i, m-1] 中
        } else {
            return m // 找到 target ,返回插入点 m
        }
    }
    // 未找到 target ,返回插入点 i
    return i
}
binary_search_insertion.rb
### 二分查找插入点(无重复元素) ###
def binary_search_insertion_simple(nums, target)
  # 初始化双闭区间 [0, n-1]
  i, j = 0, nums.length - 1

  while i <= j
    # 计算中点索引 m
    m = (i + j) / 2

    if nums[m] < target
      i = m + 1 # target 在区间 [m+1, j] 中
    elsif nums[m] > target
      j = m - 1 # target 在区间 [i, m-1] 中
    else
      return m  # 找到 target ,返回插入点 m
    end
  end

  i # 未找到 target ,返回插入点 i
end
binary_search_insertion.zig
[class]{}-[func]{binarySearchInsertionSimple}
Code Visualization

10.2.2   Case with duplicate elements

Question

Based on the previous question, assume the array may contain duplicate elements, all else remains the same.

Suppose there are multiple targets in the array, ordinary binary search can only return the index of one of the targets, and it cannot determine how many targets are to the left and right of that element.

The task requires inserting the target element to the very left, so we need to find the index of the leftmost target in the array. Initially consider implementing this through the steps shown in Figure 10-5.

  1. Perform a binary search, get an arbitrary index of target, denoted as \(k\).
  2. Start from index \(k\), and perform a linear search to the left until the leftmost target is found and return.

Linear search for the insertion point of duplicate elements

Figure 10-5   Linear search for the insertion point of duplicate elements

Although this method is feasible, it includes linear search, so its time complexity is \(O(n)\). This method is inefficient when the array contains many duplicate targets.

Now consider extending the binary search code. As shown in Figure 10-6, the overall process remains the same, each round first calculates the midpoint index \(m\), then judges the size relationship between target and nums[m], divided into the following cases.

  • When nums[m] < target or nums[m] > target, it means target has not been found yet, thus use the normal binary search interval reduction operation, thus making pointers \(i\) and \(j\) approach target.
  • When nums[m] == target, it indicates that the elements less than target are in the interval \([i, m - 1]\), therefore use \(j = m - 1\) to narrow the interval, thus making pointer \(j\) approach elements less than target.

After the loop, \(i\) points to the leftmost target, and \(j\) points to the first element less than target, therefore index \(i\) is the insertion point.

Steps for binary search insertion point of duplicate elements

binary_search_insertion_step2

binary_search_insertion_step3

binary_search_insertion_step4

binary_search_insertion_step5

binary_search_insertion_step6

binary_search_insertion_step7

binary_search_insertion_step8

Figure 10-6   Steps for binary search insertion point of duplicate elements

Observe the code, the operations of the branch nums[m] > target and nums[m] == target are the same, so the two can be combined.

Even so, we can still keep the conditions expanded, as their logic is clearer and more readable.

binary_search_insertion.py
def binary_search_insertion(nums: list[int], target: int) -> int:
    """二分查找插入点(存在重复元素)"""
    i, j = 0, len(nums) - 1  # 初始化双闭区间 [0, n-1]
    while i <= j:
        m = (i + j) // 2  # 计算中点索引 m
        if nums[m] < target:
            i = m + 1  # target 在区间 [m+1, j] 中
        elif nums[m] > target:
            j = m - 1  # target 在区间 [i, m-1] 中
        else:
            j = m - 1  # 首个小于 target 的元素在区间 [i, m-1] 中
    # 返回插入点 i
    return i
binary_search_insertion.cpp
/* 二分查找插入点(存在重复元素) */
int binarySearchInsertion(vector<int> &nums, int target) {
    int i = 0, j = nums.size() - 1; // 初始化双闭区间 [0, n-1]
    while (i <= j) {
        int m = i + (j - i) / 2; // 计算中点索引 m
        if (nums[m] < target) {
            i = m + 1; // target 在区间 [m+1, j] 中
        } else if (nums[m] > target) {
            j = m - 1; // target 在区间 [i, m-1] 中
        } else {
            j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中
        }
    }
    // 返回插入点 i
    return i;
}
binary_search_insertion.java
/* 二分查找插入点(存在重复元素) */
int binarySearchInsertion(int[] nums, int target) {
    int i = 0, j = nums.length - 1; // 初始化双闭区间 [0, n-1]
    while (i <= j) {
        int m = i + (j - i) / 2; // 计算中点索引 m
        if (nums[m] < target) {
            i = m + 1; // target 在区间 [m+1, j] 中
        } else if (nums[m] > target) {
            j = m - 1; // target 在区间 [i, m-1] 中
        } else {
            j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中
        }
    }
    // 返回插入点 i
    return i;
}
binary_search_insertion.cs
/* 二分查找插入点(存在重复元素) */
int BinarySearchInsertion(int[] nums, int target) {
    int i = 0, j = nums.Length - 1; // 初始化双闭区间 [0, n-1]
    while (i <= j) {
        int m = i + (j - i) / 2; // 计算中点索引 m
        if (nums[m] < target) {
            i = m + 1; // target 在区间 [m+1, j] 中
        } else if (nums[m] > target) {
            j = m - 1; // target 在区间 [i, m-1] 中
        } else {
            j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中
        }
    }
    // 返回插入点 i
    return i;
}
binary_search_insertion.go
/* 二分查找插入点(存在重复元素) */
func binarySearchInsertion(nums []int, target int) int {
    // 初始化双闭区间 [0, n-1]
    i, j := 0, len(nums)-1
    for i <= j {
        // 计算中点索引 m
        m := i + (j-i)/2
        if nums[m] < target {
            // target 在区间 [m+1, j] 中
            i = m + 1
        } else if nums[m] > target {
            // target 在区间 [i, m-1] 中
            j = m - 1
        } else {
            // 首个小于 target 的元素在区间 [i, m-1] 中
            j = m - 1
        }
    }
    // 返回插入点 i
    return i
}
binary_search_insertion.swift
/* 二分查找插入点(存在重复元素) */
func binarySearchInsertion(nums: [Int], target: Int) -> Int {
    // 初始化双闭区间 [0, n-1]
    var i = nums.startIndex
    var j = nums.endIndex - 1
    while i <= j {
        let m = i + (j - i) / 2 // 计算中点索引 m
        if nums[m] < target {
            i = m + 1 // target 在区间 [m+1, j] 中
        } else if nums[m] > target {
            j = m - 1 // target 在区间 [i, m-1] 中
        } else {
            j = m - 1 // 首个小于 target 的元素在区间 [i, m-1] 中
        }
    }
    // 返回插入点 i
    return i
}
binary_search_insertion.js
/* 二分查找插入点(存在重复元素) */
function binarySearchInsertion(nums, target) {
    let i = 0,
        j = nums.length - 1; // 初始化双闭区间 [0, n-1]
    while (i <= j) {
        const m = Math.floor(i + (j - i) / 2); // 计算中点索引 m, 使用 Math.floor() 向下取整
        if (nums[m] < target) {
            i = m + 1; // target 在区间 [m+1, j] 中
        } else if (nums[m] > target) {
            j = m - 1; // target 在区间 [i, m-1] 中
        } else {
            j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中
        }
    }
    // 返回插入点 i
    return i;
}
binary_search_insertion.ts
/* 二分查找插入点(存在重复元素) */
function binarySearchInsertion(nums: Array<number>, target: number): number {
    let i = 0,
        j = nums.length - 1; // 初始化双闭区间 [0, n-1]
    while (i <= j) {
        const m = Math.floor(i + (j - i) / 2); // 计算中点索引 m, 使用 Math.floor() 向下取整
        if (nums[m] < target) {
            i = m + 1; // target 在区间 [m+1, j] 中
        } else if (nums[m] > target) {
            j = m - 1; // target 在区间 [i, m-1] 中
        } else {
            j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中
        }
    }
    // 返回插入点 i
    return i;
}
binary_search_insertion.dart
/* 二分查找插入点(存在重复元素) */
int binarySearchInsertion(List<int> nums, int target) {
  int i = 0, j = nums.length - 1; // 初始化双闭区间 [0, n-1]
  while (i <= j) {
    int m = i + (j - i) ~/ 2; // 计算中点索引 m
    if (nums[m] < target) {
      i = m + 1; // target 在区间 [m+1, j] 中
    } else if (nums[m] > target) {
      j = m - 1; // target 在区间 [i, m-1] 中
    } else {
      j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中
    }
  }
  // 返回插入点 i
  return i;
}
binary_search_insertion.rs
/* 二分查找插入点(存在重复元素) */
pub fn binary_search_insertion(nums: &[i32], target: i32) -> i32 {
    let (mut i, mut j) = (0, nums.len() as i32 - 1); // 初始化双闭区间 [0, n-1]
    while i <= j {
        let m = i + (j - i) / 2; // 计算中点索引 m
        if nums[m as usize] < target {
            i = m + 1; // target 在区间 [m+1, j] 中
        } else if nums[m as usize] > target {
            j = m - 1; // target 在区间 [i, m-1] 中
        } else {
            j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中
        }
    }
    // 返回插入点 i
    i
}
binary_search_insertion.c
/* 二分查找插入点(存在重复元素) */
int binarySearchInsertion(int *nums, int numSize, int target) {
    int i = 0, j = numSize - 1; // 初始化双闭区间 [0, n-1]
    while (i <= j) {
        int m = i + (j - i) / 2; // 计算中点索引 m
        if (nums[m] < target) {
            i = m + 1; // target 在区间 [m+1, j] 中
        } else if (nums[m] > target) {
            j = m - 1; // target 在区间 [i, m-1] 中
        } else {
            j = m - 1; // 首个小于 target 的元素在区间 [i, m-1] 中
        }
    }
    // 返回插入点 i
    return i;
}
binary_search_insertion.kt
/* 二分查找插入点(存在重复元素) */
fun binarySearchInsertion(nums: IntArray, target: Int): Int {
    var i = 0
    var j = nums.size - 1 // 初始化双闭区间 [0, n-1]
    while (i <= j) {
        val m = i + (j - i) / 2 // 计算中点索引 m
        if (nums[m] < target) {
            i = m + 1 // target 在区间 [m+1, j] 中
        } else if (nums[m] > target) {
            j = m - 1 // target 在区间 [i, m-1] 中
        } else {
            j = m - 1 // 首个小于 target 的元素在区间 [i, m-1] 中
        }
    }
    // 返回插入点 i
    return i
}
binary_search_insertion.rb
### 二分查找插入点(存在重复元素) ###
def binary_search_insertion(nums, target)
  # 初始化双闭区间 [0, n-1]
  i, j = 0, nums.length - 1

  while i <= j
    # 计算中点索引 m
    m = (i + j) / 2

    if nums[m] < target
      i = m + 1 # target 在区间 [m+1, j] 中
    elsif nums[m] > target
      j = m - 1 # target 在区间 [i, m-1] 中
    else
      j = m - 1 # 首个小于 target 的元素在区间 [i, m-1] 中
    end
  end

  i # 返回插入点 i
end
binary_search_insertion.zig
[class]{}-[func]{binarySearchInsertion}
Code Visualization

Tip

The code in this section uses "closed intervals". Readers interested can implement the "left-closed right-open" method themselves.

In summary, binary search is merely about setting search targets for pointers \(i\) and \(j\), which might be a specific element (like target) or a range of elements (like elements less than target).

In the continuous loop of binary search, pointers \(i\) and \(j\) gradually approach the predefined target. Ultimately, they either find the answer or stop after crossing the boundary.

Feel free to drop your insights, questions or suggestions