前言:本博客用于更新力扣的数组模块,关于二分法的题解。
1.数组的关键点
①连续的地址空间,所以两要素必不可少:首地址+大小,任何时候都要注意此两要素,首地址+大小。
力扣704. 二分查找 - 力扣(LeetCode)
二分查找顾名思义就是采用二分法来查找有序数组,说人话就是一半一半的查找,其基本思维就是四个大字:不重不漏!!!即通过正确划分边界,来确保二分查找的时候不重不漏!
解题思路:二分法步骤
①while循环当中每次求解下标为middle的值与target进行大小对比;
②根据middle的值和target的对比,更新left,right和middle;
③其中更新right的时候,就需要注意边界问题,也就是左闭右闭还是左闭右开。
C语言实现
//二分法两种确定边界的方法:左闭右闭,左闭右开。
//左闭右闭
int search(int* nums, int numsSize, int target) //[left,right]
{
//二分法定义边界left,right,是左闭右闭[left,right],还是左闭右开[left,right),取决于right包括不包括numsSize
int left = 0;
int right = numsSize - 1;
int middle = 0;
//二分法的循环条件定义,左闭右闭,还是左闭右开来定义while循环体的条件
while (left <= right)
{
//二分法的具体实现
middle = (left + right) / 2;
if (target < nums[middle])
{
right = middle - 1;
}
else if (target > nums[middle])
{
left = middle + 1;
}
else
return middle;
}
return -1;
}
C++实现
//左闭右闭
class Solution
{
public:
int search(vector<int>& nums, int target)
{
int left = 0;
int right = nums.size() - 1;
while(right >= left)
{
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;
}
};
C和C++对比
都是常规思路,没有引入容器
在C++的实现过程中需要注意的是数组的求大小公式:nums.size();
力扣:69. x 的平方根 - 力扣(LeetCode)
审题:求解x的算术平方根,要求不能使用内置函数和**运算符来求解。首先写出数学公式:ans * ans = x,现在的要求是求解ans,有一种数学思想是,(无限逼近 ≈ 等于),其实无限逼近就可以看作是等于,那么我们之前在一个有序的数组当中查找一个元素的时候,是不是也是通过移动左右边界,然后求解这个无限逼近的值就是最终的值呢?所以这个题同样可以采用二分法来求解,主要思维就是:二分法的另一核心思想之(无限逼近 ≈ 等于)
解题思路:二分法步骤
和上述的二分法一模一样,直接看代码注释就ok
C语言实现
int mySqrt(int x)
{
int left = 0;
int right = x;
int ans = -1;
while(left <= right)
{
int middle = left + (right - left) / 2; //防止下标溢出
if((long long)middle * middle <= x) //long long 是防止middle的平方溢出int类型
{
ans = middle; //求取middle无限逼近middle * middle = x
left = middle + 1; //移动left使其找见无限逼近最终值的middle
}
else
{
right = middle - 1;
}
}
return ans;
}
C++实现
同C
力扣35. 搜索插入位置 - 力扣(LeetCode)
审题:在一个有序数组里面,查找是否存在target,如果存在返回下标;如果不存在,返回其插入这个有序数组当中的位置(下标)。
解题思路:二分法
首先看到题目,第一反应是遍历这个数组,在其中找是否存在这样的target,然后返回相应的下标,但是当看到如下两点的时候,就可以考虑使用二分法:在一个有序数组当中;查找一个目标值,注意是一个。
二分法步骤回顾:
①定义三个变量:头部head,中部middle,尾部tail;、
②while循环(head <= tail)左闭右闭,(head < tail)左闭右开;
③通过比较middle和target的值,进行head和tail的二分收缩。
细节注意:左闭右闭和左闭右开两种对应的while和tail的更新不同,因为要做到不重不漏!
C语言实现
int searchInsert(int* nums, int numsSize, int target)
{
//二分法三个变量:head,middle,tail;然后判断middle是否就是target;
int head = 0;
int tail = numsSize - 1; //左闭右闭(因为tail是数组当中的最后一个元素)
int middle = 0;
while(head <= tail)
{
middle = (head + tail) / 2;
if(nums[middle] > target)
{
tail = middle - 1; //因为tail包含在里面,是右闭模型,所以不能再次包含
}
else if(nums[middle] < target)
{
head = middle + 1; //同上
}
else
{
return middle;
}
}
return head; //while当中是head <= tail的时候,按理来说是head + 1,但是力扣上面head可以过,有点疑惑
//因为head的值更新是head = middle + 1;所以此时当head = tail 的时候,head已经是+1了,所以直接return head,而不是head + 1;
}
C++实现
完全同C,只是numsSize = nums.size()
力扣34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
审题:在一个递增数组当中,寻找与目标值target相同的开始位置和结束位置,然后用数组的形式返回这两个位置。
解题思路:二分法
在一个有序的数组当中查找元素,有序和查找,满足二分法的条件,但是这个题有个特点是查找多个元素,那么查找多个元素怎么处理呢,因为之前接触的都是查找一个元素?
这个题其实细究的话是查找两个元素:一个最左边的,一个最右边的,所以基本思路是二分法,然后一个一个的查找,具体思路如下:
①二分法分两步进行,也就是定义两个函数,先找重复元素最左边的下标;
②再找重复元素最右边的下标;
③最后将两个下标存进数组当中返回。
重点:当找见第一个左边界之后,tail开始收缩至middle -1处,继续用二分法逻辑找下一个左边界;当找见一个右边界之后,head开始收缩至middle +1处,继续用二分法找下一个有边界;同时记录每次等于target的边界值
C语言实现
//难点:之前接触的二分法都是找一个元素,这个是找多个元素,二分法如何找多个元素呢?采用两个二分法,一个二分法找重复target最左边的,一个找target最右边的。
//二分法找左边界
int searchLeft(int* nums, int numsSize, int target)
{
//二分基本步骤,头,中,尾
int head = 0;
int tail = numsSize - 1; //右闭
//定义最左边的初始值是-1
int leftBorder = -1;
while(head <= tail)
{
int middle = (head + tail) / 2;
//当找到一个后,移动尾部tail,继续收缩,知道找见数组最左边的target对应的下标
if(nums[middle] == target)
{
//先记录leftBorder
leftBorder = middle;
//收缩大数组的尾部继续寻找
tail = middle - 1;
}
else if(nums[middle] < target)
{
head = middle + 1;
}
else
{
tail = middle - 1;
}
}
return leftBorder;
}
//右边界同理:找见第一个右边的之后,head开始收缩,继续找,直到找见最后一个,没找见就是-1
int searchRight(int* nums, int numsSize, int target)
{
//二分基本步骤,头,中,尾
int head = 0;
int tail = numsSize - 1; //右闭
//定义最右边的初始值是-1
int rightBorder = -1;
while(head <= tail)
{
int middle = (head + tail) / 2;
//当找到一个后,移动头部head,继续收缩,知道找见数组最右边的target对应的下标
if(nums[middle] == target)
{
//先记录rightBorder
rightBorder = middle;
//收缩大数组的头部继续寻找
head = middle + 1;
}
else if(nums[middle] < target)
{
head = middle + 1;
}
else
{
tail = middle - 1;
}
}
return rightBorder;
}
int* searchRange(int* nums, int numsSize, int target, int* returnSize)
{
int leftBorder = searchLeft(nums, numsSize, target);
int rightBorder = searchRight(nums, numsSize, target);
//开辟存放左右边界的数组,大小为2;
*returnSize = 2;
int * returnArr = (int*)malloc(2 * sizeof(int));
returnArr[0] = leftBorder;
returnArr[1] = rightBorder;
return returnArr;
}
C++
打完球来写