704 二分查找
最重要的就是分类讨论好二分,二分看着好写边界case还是需要测试的。大家需要注意二分的几种情况
当l = 0, r = n的时候因为r这个值我们在数组中无法取到,while(l < r) 是正确写法
当l = 0, r = n - 1的时候因为r这个值我们在数组中可以取到,while(l <= r) 是正确写法 主要看能不能取到这个值
二分法有多种写法,末尾是开区间闭区间都可以解出寻找单个元素和寻找边界的题目,只需要注意相应的是l < r还是l <= r,每次取mid还是取mid加减一即可。建议理解后背熟一套模板,不要搞混。
关于二分法和移除元素的共性思考
关于二分法和移除元素的共性思考这两题之间有点类似的,他们都是在不断缩小 left 和 right 之间的距离,每次需要判断的都是 left 和 right 之间的数是否满足特定条件。对于「移除元素」这个写法本质上还可以理解为,我们拿 right 的元素也就是右边的元素,去填补 left 元素也就是左边的元素的坑,坑就是 left 从左到右遍历过程中遇到的需要删除的数,因为题目最后说超过数组长度的右边的数可以不用理,所以其实我们的视角是以 left 为主,这样想可能更直观一点。用填补的思想的话可能会修改元素相对位置,这个也是题目所允许的。
其实二分还有很多应用场景,有着许多变体,比如说查找第一个大于target的元素或者第一个满足条件的元素,都是一样的,根据是否满足题目的条件来缩小答案所在的区间,这个就是二分的本质。另外需要注意,二分的使用前提:有序数组
小tips:
根据题目的数据量范围选择合适的算法,比如数据量是105,那就只能使用O(nlogn)复杂度以下的算法了,使用O(n2)是会超时的;而如果数据量只有100或者1000,则可以果断的采用暴力方法(一般是O(n^2))进行求解
二分的最大优势是在于其时间复杂度是O(logn),因此看到有序数组都要第一时间反问自己是否可以使用二分。
例题讲解
左闭右闭版
若要对该数组进行二分查找要注意以下两点易错点:
while 循环变量中 left <= right。记住一定是小于等于而不是小于这是因为我们写的是左闭右闭版 left 是可以等于 right 的。
当比较发现 nums[mid] 的值小于 target 的值时, 跟新右区间的左边界,left = mid + 1 ! ! !。而不是 left = mid 再次强调我们的区间是左闭右闭的。我们已经判断出 nums[mid] 的值小于 target 了,说明 muns[mid] 一定不是我们搜索的值,接下来搜索的区间一定不包含这个数值。
代码如下:
class Solution {
public int search(int[] nums, int target) {
//左闭右闭
int left = 0;
int right = nums.length - 1;
int mid;
while( left <= right ){
mid = (left + right) / 2;
if(nums[mid] < target){
left = mid + 1;
}
else if(nums[mid] > target){
right = mid - 1;
}
else return mid;
}
return -1;
}
}
27 移除元素数值
关键是要理解 fast 和 slow 指针的作用。fast 指针用来寻找新数组中需要的元素,slow 指针用来指明新数组下标的位置。我们把 fast 指针所获取的值赋值给新数组所对应下标的位置。
代码如下:
class Solution {
public int removeElement(int[] nums, int val) {
int slow = 0;
for(int fast = 0; fast < nums.length ; fast++){
if(nums[fast] != val){
nums[slow ++] = nums[fast];
}
}
return slow;
}
}