Binary Search
Template
#![allow(unused)] fn main() { fn check(mid: i32) -> bool { todo!() } fn binary_search() -> i32 { let mut left = 0; let mut right = i32::MAX; while left < right { let mid = left + (right - left) / 2; if check(mid) { left = mid; } else { right = mid - 1; } } left } }
Key Approaches
Credit to @Lee215
-
use
left < right
orleft <= right
to make the binary search process easier, we do not handle the
left == right
case in the loop. Instead, we will try to make the leastleft
index to point to the correct answer that we are looking for. -
use
mid = left + (right - left) / 2
ormid = left + (right - left + 1) / 2
- use
mid = left + (right - left) / 2
to find the index of the first valid element - use
mid = left + (right - left + 1) / 2
to find the index of the last valid element
- use
Search for the first index of the valid value
0 1 2 3 4 5 6 7 8 9
--- --- --- --- --- --- --- --- --- ---
| 0 | 0 | 1 | 1 | 2 | 2 | 2 | 3 | 3 | 3 |
--- --- --- --- --- --- --- --- --- ---
^
|
target
^ ^ ^ ^ ^ ^
| | | | | |
valid
fn main() { let list = vec![0, 0, 1, 1, 2, 2, 2, 3, 3, 3]; let k = 2; let res = binary_search_first_valid(list, k); println!("Result: {:?}", res); } fn binary_search_first_valid(list: Vec<i32>, target: i32) -> usize { fn check(list: &Vec<i32>, mid: usize, target: i32) -> bool { list[mid] >= target } let mut left = 0; let mut right = list.len(); while left < right { let mid = left + (right - left) / 2; if check(&list, mid, target) { right = mid; } else { left = mid + 1; } } left }
Search for the last index of the valid value
0 1 2 3 4 5 6 7 8 9
--- --- --- --- --- --- --- --- --- ---
| 0 | 0 | 1 | 1 | 2 | 2 | 2 | 3 | 3 | 3 |
--- --- --- --- --- --- --- --- --- ---
^
|
target
^ ^ ^ ^ ^ ^ ^
| | | | | | |
valid
fn main() { let list = vec![0, 0, 1, 1, 2, 2, 2, 3, 3 ,3]; let k = 2; let res = binary_search_last_valid(list, k); println!("Result: {:?}", res); } fn binary_search_last_valid(list: Vec<i32>, target: i32) -> usize { fn check(list: &Vec<i32>, mid: usize, target: i32) -> bool { list[mid] <= target } let mut left = 0; let mut right = list.len(); while left < right { let mid = left + (right - left + 1) / 2; if check(&list, mid, target) { left = mid; } else { right = mid - 1; } } left }
Search for the exact index of a valid element
0 1 2 3 4 5 6 7 8 9
--- --- --- --- --- --- --- --- --- ---
| 0 | 0 | 1 | 1 | 1 | 1 | 2 | 3 | 3 | 3 |
--- --- --- --- --- --- --- --- --- ---
^
|
target
The first approach is to use the rust built-in binary search method of a Vec
#![allow(unused)] fn main() { let list = vec![0, 0, 1, 1, 1, 1, 2, 3, 3 ,3]; let target = 2; let res = list.binary_search(&target); println!("Result: {:?}", res); // Ok(6) }
Another benefit is the built in binary search method will also yield the location where the given value should be inserted to keep the Vec
in the sorted order.
#![allow(unused)] fn main() { let list = vec![0, 0, 1, 1, 1, 1, 3, 3, 3 ,3]; let target = 2; let res = list.binary_search(&target); println!("Result: {:?}", res); // Err(6) }
Let's see how can we adapt the "Search for the first index of the valid value" to do the same as the above built-in binary search function.
fn main() { let list = vec![0, 0, 1, 1, 2, 3, 3, 3, 3, 3]; let k = 2; let res = binary_search(list, k); println!("Search for 2 in the list [0, 0, 1, 1, 2, 3, 3, 3, 3, 3]"); println!("Result: {:?}", res); let list = vec![0, 0, 1, 1, 2, 3, 3, 3, 3, 3]; let k = 4; let res = binary_search(list, k); println!("Search for 4 in the list [0, 0, 1, 1, 2, 3, 3, 3, 3, 3]"); println!("Result: {:?}", res); } fn binary_search(list: Vec<i32>, target: i32) -> Result<usize, usize> { fn check(list: &Vec<i32>, mid: usize, target: i32) -> bool { list[mid] >= target } let mut left = 0; let mut right = list.len(); while left < right { let mid = left + (right - left) / 2; if check(&list, mid, target) { right = mid; } else { left = mid + 1; } } if left < list.len() { Ok(left) } else { Err(left) } }