704. 二分查找
思路
给定的数组有序且不重复,因此可以使用二分法解题
解题方法
二分法通过不断拆分数组来定位 targe,因此数组的定义就十分重要,这里有两种情况
- 左闭右开
- 左闭右闭
不管哪种情况,都需要确保两件事
- 遍历时,不能遗漏数组合法区间内的元素
- 构建数组时,不能遗漏也不能多添加元素
因此,对于左闭右闭的情况
- while 循环时,
while
定义了当前数组的所有元素,且右边界合法,因此left <= right
- 构建下一个子区间时,需要保证左右边界合法且不能囊括已排除的元素
对于左闭右开的情况
- while 循环时,
while
定义了当前数组的所有元素,且右边界不合法,因此left < right
- 构建下一个子区间时,需要保证右边界不合法,因此右边界可以为已排除元素,左边界则不能
复杂度
-
时间复杂度:
O ( l o g n ) O(logn) O(logn) -
空间复杂度:
O ( 1 ) O(1) O(1)
Code
Rust
fn main() {
println!("============= 左闭右闭 =================");
let nums = vec![5];
let target_1 = -5;
// let target_2 = 12;
let result = search_1(&nums, target_1);
// let result_2 = search_1(&nums, target_2);
println!("{}", result);
// println!("{}", result_2);
// println!("============= 左闭右开 =================");
let result_3 = search_2(&nums, target_1);
// let result_4 = search_2(&nums, target_2);
println!("{}", result_3);
// println!("{}", result_4);
}
/**
* 左闭右闭
*/
fn search_1(nums: &Vec<i32>, target: i32) -> i32 {
let mut left: i32 = 0;
let mut right: i32 = (nums.len() - 1) as i32;
// 遍历需要遍历到合法区间的每个数值
while right >= left {
let middle = (left + right) / 2;
// 右半边
if nums.get(middle as usize).unwrap() < &target {
// 新区间不能包括旧区间的数值
left = middle + 1;
}
// 左半边
else if nums.get(middle as usize).unwrap() > &target {
// 新区间不能包括旧区间的数值
// 注意,如果 middle 的类型为 usize,如果 middle = 0 - 1,则会导致数值溢出
right = middle - 1;
}
// 找到
else {
return middle as i32;
}
}
-1
}
/**
* 左闭右开
*/
fn search_2(nums: &Vec<i32>, target: i32) -> i32 {
let mut left: usize = 0;
let mut right: usize = nums.len() - 1;
// 左闭右开,因此最右边的数值不是合法区间,不能遍历到右极值
while right > left {
let middle = (left + right) / 2;
if nums.get(middle).unwrap() < &target {
left = middle + 1;
} else if nums.get(middle).unwrap() > &target {
// 构建左半边时要特别注意,合法区间内不能漏值,同时最右极值不在区间内
// 因此,让右节点为之前的 middle 即可
// 注意,这里不会有负数情况,因此可以用 usize
right = middle;
} else {
return middle as i32;
}
}
-1
}
Java
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right) {
int middle = (left + right) / 2;
if (nums[middle] > target) {
right = middle - 1;
} else if (nums[middle] < target) {
left = middle + 1;
} else {
return middle;
}
}
return -1;
}
}
27. 移除元素
思路
- 要求O(1),因此就不能使用另一个数组来暂存,只能使用指针在原数组中操作
- 删除操作,在数组中实际上就是覆盖操作
解题方法
使用双指针法进行解题
快指针负责遍历数组,慢指针负责组装(覆盖)目标数组;如果是非删除数据,则将快指针的数据赋值给慢指针
复杂度
-
时间复杂度:
O ( n ) O(n) O(n) -
空间复杂度:
O ( 1 ) O(1) O(1)
Code
Rust
fn main() {
let mut nums:Vec<i32> = vec![3, 2, 2, 3];
let val = 3;
let length = remove_element(&mut nums, val);
println!("{}", length);
}
fn remove_element(nums: &mut Vec<i32>, val: i32) -> i32 {
let mut slow_index = 0;
for pos in 0..nums.len() {
if nums[pos] != val {
nums[slow_index] = nums[pos];
slow_index += 1;
}
}
return slow_index as i32;
}
Java
class Solution {
public int removeElement(int[] nums, int val) {
// o1 的空间
int slowIndex = 0;
// 快指针遍历数组
for(int fastIndex = 0; fastIndex < nums.length; fastIndex++) {
// 如果是非删除数据,则将快指针的数据赋值给慢指针
if (nums[fastIndex] != val) {
nums[slowIndex++] = nums[fastIndex];
}
}
return slowIndex;
}
}