代码随想录(二分查找)
二分查找
前言
此篇是基于代码随想录里关于二分查找相关习题的详细分析,虽然基本上每题都有别的方法,但是这里只对“二分查找思想”具体在每道题里是如何抽象的,如何解决的?这类问题做具体分析!不对二分查找的原理具体分析
另外如果你完全不知道或者忘了什么是二分查找,建议观看懒猫老师和liweiwei1419的视频,十分详细,十分具体,通俗易懂,好了,正片开始。
随想录例题
这道题就是一个单纯的对于二分查找的使用,但是我们还是要从中体会二分查找的目的是什么,怎么样实现的,我们为什么会想到用它来解决?我觉得这个思考对于后面的题目或者说是变式,如果我在不告诉你他们可以用二分查找来实现的时候你又是否能想到呢?其实我在学习了一些算法后发现,其实代码基本上都不会特别长,但是难就难在你是否具备了良好的算法思维以及对于整体代码细节的处理,例如溢出或者超时的问题,有时候即便代码没问题还是AC不了,你又是否还有耐心继续写下去呢?
对于本题目我们可以按照以下四步来解决:
1.读题,分析题意
本题说白了就是给一个数,让我们在一个数组里查找,如果有就返回下标,没有就返回-1.
2.分析搜索范围
从0到numsize-1
3.如何缩小区间(如果可以)
4.如何解决超时和溢出(如果有)
代码演示
其实核心就是通过两个分别指向首尾的指针,在中间元素与target比较后,不断不断缩小区间的问题,懒猫老师讲的就十分详细。
#include <stdio.h>
//二分查找的基本思想是:如果待查找的数组有序,则可以通过比较待查找元素与中间元素的大小关系,从而缩小待查找区间,直到待查找元素被找到为止。
int binary_search(const int *nums,int numsize,int target)
{
int left = 0;
int right = numsize - 1;
while(left <= right)
{
int mid = (left + right) / 2;
if(nums[mid] == target)
{
return mid;
}
else if(nums[mid] > target)
{
right = mid - 1;
}
else
{
left = mid + 1;
}
}
return -1;//没有找到的情况下返回-1
}
//左闭右开写法 与上面的左闭右闭的写法的不同主要在循环的终止条件和low跟high两个指针怎么移动上
int search(const int *nums,int numsize,int target)
{
int low=0;
int high=numsize-1;
while (low<high)
{
int mid=(low+high)/2;
if(nums[mid]>target)
{
high=mid;
}
else if(nums[mid]<target)
{
low=mid;//为什么low跟high指针不加1呢?
//是因为当我们写的是左闭右开的区间时 每一次查找后的区间是不包含mid所指的元素的 所以是不能跳过mid的
} else{
return mid;
}
}
return -1;
}
既然本题作为例题,我就附上了卡尔老师在课程上提到的两种写法,这两种写法都可以,但其实具体是左闭右闭区间还是左闭右开区间,熟练一种就行,我们真正要把握的是对于临界情况掌控和到底在执行一次循环后区间是怎样的?
- 1.指针移动的不同
对于左闭右开的写法,比如给是{1 , 2,3,,7,10},target=7在执行一次循环后,mid指针指向的是3,那么right指针就要向左移动,执行right=mid,为什么不用+1了呢,正是因为我们写的是左闭右开,下一个搜索范围必须要包含我们的right,即现在我们mid指向位置上的元素 - 2.循环终止条件的不同(这是左闭右闭的情况)
代码随想录相关题目
35.搜索插入位置
这题还是二分查找的思想,唯一不同的就是题目要求找不到target的时候,把target顺序插入到数组中,这个怎么实现。
我是这么理解的:二分查找的思想的核心就是不断缩小target出现的区间,即便到最后target,没有找到,我们的left和right还是会重合指向我们target应该被顺序插入的位置,即return right or left
代码演示
#include <stdio.h>
int searchInsert(int* nums, int numsSize, int target) {
int left = 0;
int right = numsSize - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else {
return mid;
}
}
// 如果跳出循环就代表target不存在于数组中
// 正确的插入位置是left
return left;
}
//暴力解法
int searchInsert2(int* nums, int numsSize, int target)
{
int length=numsSize;
if(nums[length-1]<target)
{
return length;
}
//如果第一个数都大于target,那么我们只需要将后面所有元素向后移动一位,然后把target放在nums[0]的位置上就好
if(nums[0]>=target)
{
return 0;
}
//通过上面两个判断,我们就可以知道如果执行到这里那么target需要被插入在数组中间的位置
for (int i=1;i<length;i++)
{
if(nums[i]>=target)
{
return i;
}
}
return -1;
}
int main() {
int nums[] = {1, 3, 5, 6};
int result = searchInsert(nums, 4, 2);
printf("%d\n", result); // 输出应为1
return 0;
}
34. 在排序数组中查找元素的第一个和最后一个位置
这道题难度高一些,难在很多细节和边界需要我们去思考到底怎么设置,但本质上还是查找一个有范围的数
代码示例
#include <stdio.h>
#include <stdlib.h>
int findStartingIndex(int* nums, int numsSize, int target) {
int left = 0, right = numsSize - 1;
int result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
result = mid;
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
}
int findEndingIndex(int* nums, int numsSize, int target) {
int left = 0, right = numsSize - 1;
int result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
result = mid;
left = mid + 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
}
int* searchRange(int* nums, int numsSize, int target, int* returnSize) {
int* returnArray = (int*)malloc(sizeof(int) * 2);
*returnSize = 2;
returnArray[0] = findStartingIndex(nums, numsSize, target);
returnArray[1] = findEndingIndex(nums, numsSize, target);
return returnArray;
}
69.X的平方根
对于本题建议还是按步骤分析:
1.读题,分析题意
我们可以通过示例二发现当我们找不到一个整数正好等于8时,就返回一个平方之后最接近它的最大整数2,而不是3,因3的平方大于8,所以这道题目其实就是让我们找平方之后小于等于x的最大整数。
2.分析搜索范围
[0,x]
3.如何缩小区间(如果可以)
我们可以发现一个数(>=4)开算术平方根之后是不会大于他的一半的,这样搜索范围就从原来的x变成了2/x,但是在进行查找之前需要对0,1,2,3这几个特例进行讨论,不难发现1,2,3,在开算数平方根之后都只需要返回1即可,那么就只需要分两种情况
4.如何解决超时和溢出(如果有)*
我们注意左下角的提示,x的范围是整形的最大范围,如果我们平方后很可能就出现了溢出的情况,也就是说mid*mid的范围超过了int类型,此时他就会显示负数,所以我们采用mid<x/mid的方式或者一开始直接把mid定义为long来解决
代码示例
#include <stdio.h>
int mySqrt(int x)
{
if(x==0){
return 0;
}
if(x<4)
{
return 1;
}
int left=2;
int right=x/2;
while (left<=right)
{
int mid=(left+right)/2;
if(mid<x/mid)
{
left=mid+1;
} else if(mid>x/mid){
right=mid-1;
} else{
return mid;
}
}
return right;
}
367.有效的完全平方数
在读完题目之后就会感觉跟上面一题十分类似,最大的区别在于返回的要求不同而已,注意:如果要在C语言程序中运行
请先宏定义一下布尔类型还有true和false的值
代码演示
bool isPerfectSquare(int num) {
if (num == 1) return true; // 对于此题已经说了大于1所以可以省去
int left = 1;
int right = num / 2;
while (left <= right) {
long mid = (left + right) / 2;
if (mid * mid > num) {
right = mid - 1;
} else if (mid * mid < num) {
left = mid + 1;
} else {
return true;
}
}
return false;
}
## 结语
最后我想说的就是,如果你因为一些非常小的问题而导致代码一直AC不了,不要想太多,这是非常正常的情况,也不要因为看着力扣上easy的题没什么思路而emo,因为简单题基本上要么就是暴力就能过或者就是有直接的库函数来解决,你不会只不过还不知道而已,请一定坚持!那么祝愿看到这里的你有美好的一天,如果觉得我的贴子不错,麻烦点个关注,蟹蟹!