- Is the array empty?
- Contain duplicates?
Input: nums = [1,3,5,7], target = 5
Output: 2
Input: nums = [1,3,5,7], target = 6
Output: 1
- The target is smaller than the first element.
Input: nums = [1,3,5,7], target = 0
Output: 0
- The target is larger than the last element.
Input: nums = [1,3,5,7], target = 8
Output: 5
To find the target value or the index to insert, then it indicates that we're going to search the smallest number which is equals to or greater than target
(it's "floor", "left bound").
[1, 3, 5, 6], target = 5
^ // target position
[1, 3, 5, 6], target = 2
^ // Insert position, 3 is the smallest number which is equals to or greater than 2.
How about
the largest number which is equals to or smaller than target
? No, it's not the correct insertion position. From the example[1, 3, 5, 6], target = 2
, the correct insertion position is at value3
, not1
.
How can we find the smallest number which is equals to or greater than target
? For the normal binary search, we have the following conditions:
- If
target < nums[middle]
, we should search the left part. (The normal binary search condition) - If
nums[middle] < target
, we should search the right part. (The normal binary search condition) - If
target == nums[middle]
, the critial part is here, we should search the left part, because we are not sure if themiddle
is the smallest number >=target
, so we keep searching the left part.
This approach is also applicable to the array with duplicates.
The
left
pointer is the index of the smallest number >=target
, and theright
pointer is the index of the largest number <=target
. For more explanation detail regarding the return valueleft
index, you can check below.
fun searchInsert(nums: IntArray, target: Int): Int {
var left = 0
var right = nums.size - 1
while (left <= right) {
val middle = left + (right - left) / 2
if (target < nums[middle]) right = middle - 1 // Normal binary search condition
else if (nums[middle] < target) left = middle + 1 // Normal binary search condition
else if (target == nums[middle]) right = middle - 1 // The critical part, we should search the left part.
}
// Why returning left? See below explanation.
// The possible range is `[0, size]`
return left
}
// Or equivalently
fun searchInsert(nums: IntArray, target: Int): Int {
var left = 0
var right = nums.size - 1
while (left <= right) {
val middle = left + (right - left) / 2
// As same as normal binary search.
if (nums[middle] == target) return middle
else if (nums[middle] < target) {
left = middle + 1
} else if (target < nums[middle]) {
right = middle - 1
}
}
return left
}
As we have while (left <= right)
, in the last iteration we have the following cases:
When size is odd, or simply size is 1:
[..., X, ...]
L
M
R
- If
target <= nums[middle]
, thenmiddle
should be the correct answer and we should search left part by movingright = middle - 1
. At the moment, the while loop terminates,left
is at the same position ofmiddle
, so it is the correct answer.
......., X, M, ...
target L
R
- If
nums[middle] < target
,middle + 1
should be the correct answer, and we should search right part by movingleft = middle + 1
. At the moment, the while loop terminates,left
is the correct answer.
..., M, X, ...
L target
R
When size is even, or simply size is 2:
[..., X, X, ...]
L R
M
- If
target <= nums[middle]
:middle
is the correct answer, as same as case 1. - If
nums[middle] < target
: We should search right part, we moveleft = middle + 1
which is equal toright
, then it becomes the caseleft == middle == right
, and we have already explained the logic above.
Another Important Idea!!
Or you can think in this way, we are looking for the first element that satifies the condition target <= num
, our condition is target <= num
. and we have the following array, X
indicates the numbers that doesn't meet the condition (the numbers < target
), and O
indicate the number met the condition (the numbers >= target
), then the smallest number which is equals to or greater than target
is the first O
we want:
[X, X, X, O, O, O, O]
^ // target
L R
It's similar to First Bad Version.
fun searchInsert(nums: IntArray, target: Int): Int {
var left = 0
var right = nums.size - 1
while (left <= right) {
val middle = left + (right - left) / 2
// Looking for the first element that satifies the condition.
if (meetCondition(target, nums[middle])) {
right = middle - 1
} else {
left = middle + 1
}
}
return left
}
// Condition: target <= num
private fun meetCondition(target: Int, num: Int): Boolean {
return target <= num
}
The left
pointer is in the range [0, size]
:
[O, O, O, O]
^ // Not found
[X, X, O, O]
^ // target
[X, X, X, X], n
^ // target
Try the example with only 2 element.
Position: 0, 1
Array: [7, 9]
3 => [*3, 7, 9] 0 // The result will be 0 if target is smaller than the first element.
7 => [*7, 7, 9] 0
8 => [7, *8, 9] 1
9 => [7, *9, 9] 1
10 => [7, 9, *10] 2 // The result will be `size` if target is larger than the last element.
^ ^
Target Insert Position
How to find the last element which is <= target
(It' "ceiling" or "right bound", not the insert position)?
It's the pattern:
[O, O, O, X, X, X, X]
^ // target
We search the last element that satisfies the condition num <= target
.
The right
pointer is in the range [-1, size - 1]
:
// Possible range, determinicis
_, [X, X, X, X]
^ // Not found
[X, X, O, O]
^ // target
[O, O, O, O]
^ // target
[1, 3, 7] target = 5
O O X
^
[1, 3, 3, 3] target = 3
O O O O
^
-1 [7, 7, 7] target = 5 // return -1
X X X
^
L
M
R
[1, 1, 1] target = 5
O O O
^
L
M
R
fun search(nums: IntArray, target: Int): Int {
var left = 0
var right = nums.size - 1
while (left <= right) {
val middle = left + (right - left) / 2
if (target < nums[middle]) {
right = middle - 1
} else if (target == nums[middle]) { // We are not sure if it's the last element, so we keep searching the right part.
left = middle + 1
} else if (nums[middle] < target) {
left = middle + 1
}
}
return right
}
// Or equivalently, we search the last element that satisfies the condition `num <= target`.
fun search(nums: IntArray, target: Int): Int {
var left = 0
var right = nums.size - 1
while (left <= right) {
val middle = left + (right - left) / 2
// Looking for the last element that satisfies the condition.
if (meetCondition(target, nums[middle])) {
left = middle + 1
} else {
right = middle - 1
}
}
// The possible range is `[-1, size - 1]`
return right
}
// Condition: num <= target
private fun meetCondition(target: Int, num: Int): Boolean {
return num <= target
}