【刷题日记001】数组——二分法
本系列主要用来记录2022年寒假刷LeetCode一些笔记。写博客的目的是为了敦促自己在家不要摆烂努力学习otz。主要是根据 代码随想录 的刷题计划进行的。代码是LeetCode官方题解及代码随想录提供的版本。
二分查找
如果题目中说了有序数组,数组元素不重复则考虑使用二分查找。二分查找要注意边界条件。在二分查找过程中,在while寻找中每一次边界处理都要根据区间定义来操作。
区间定义分为左闭右闭[left,right] 和 左闭右开[left,right) 两种。
左闭右闭[left,right]
int left = 0, right = nums.size()-1;//定义区间左闭右闭
while(left <= right){ // left==right时区间依旧有效,故采用<=
int mid = (right - left) / 2 + left; // 与(left+right)/2等价,这么写是为了防止相加时溢出。
if(nums[mid] > target){
right = mid - 1; //如果中间值比target大,说明要在更小的一半寻找。
}
else if(nums[mid] < target){
left = mid + 1; //如果中间值比target大,说明要在更小的一半寻找。
}
else return mid; //若target == nums[mid]表示找到,返回位置
}
return -1;//未找到
左闭右开[left,right)
int left = 0, right = nums.size();//定义区间左闭右开[left, right)
while(left < right){ // left==right时区间无效,故采用<
int mid = (right - left) / 2 + left; // 与(left+right)/2等价,这么写是为了防止相加时溢出。
if(nums[mid] > target){
// 这里right = mid是因为寻找的区间是左开右闭
// 下一个查询区间依旧不会比较nums[mid]
right = mid ; //如果中间值比target大,说明要在更小的一半寻找。
}
else if(nums[mid] < target){
left = mid + 1; //如果中间值比target大,说明要在更小的一半寻找。
}
else return mid; //若target == nums[mid]表示找到,返回位置
}
return -1;//未找到
leetcode相关题目
704.二分查找
题目描述:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
class Solution {
public:
int search(vector<int> & nums, int target)
{
int low = 0, high = nums.size() - 1;
int mid;
while (low <= high)
{
mid = (high - low)/2 + low;//高效算mid mid = low+high/2有可能溢出
if(nums[mid] > target)
high = mid - 1;
else if(nums[mid] < target)
low = mid + 1;
else return mid;
}
return -1;
}
};
35.搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
题解:和上题类似。若查找失败时,返回插入位置为当前low指向的位置。因为二分法会找到离目标值最近的值得前一个值,若target < nums[mid]则low会指向mid后得一个数。即要插入的位置【手动模拟一下】
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int low = 0, high = nums.size() - 1;
int mid;
while(low <= high)
{
mid = (high - low)/2 + low;
if(nums[mid] > target)
high = mid - 1;
else if(nums[mid] < target)
low = mid + 1;
else return mid;
}
return low;
}
};
34.在排序数组中查找元素的第一个和最后一个位置
题目:给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
题解:这道题有点难,因为涉及到多个重复数字。这里寻找target在数组中的位置分三种情况:
- target在数组范围外,return{-1,-1}
- target在数组范围内,但不存在target,return{-1,-1}
- target在数组范围内,且存在target,return target的左右边界范围
再利用二分法找到左右边界。
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
// 处理三种情况
int leftBoard = getLeftBoard(nums, target);
int rightBoard = getRightBoard(nums, target);
if(leftBoard == -2 || rightBoard == -2) return {-1, -1};
if(rightBoard -leftBoard > 1) return {leftBoard + 1, rightBoard - 1};
return {-1, -1};
}
private:
// 二分法寻找右边界
int getRightBoard(vector<int>& nums, int target)
{
int low = 0, high = nums.size() - 1;
// 记录是否存在右边界
int rightBoard = -2;
while(low <= high){
int mid = (high - low) / 2 + low;
if(nums[mid] > target)
high = mid - 1;
else{
low = mid + 1;
rightBoard = low;
}
}
return rightBoard;
}
//寻找左边界
int getLeftBoard(vector<int>& nums, int target)
{
int low = 0, high = nums.size()-1;
int leftBoard = -2;
while(low <= high)
{
int mid = (high - low) / 2 + low;
if(nums[mid] >= target)
{
high = mid -1;
leftBoard = high;
}
else
low = mid + 1;
}
return leftBoard;
}
};
69.Sqrt(x)
题目:给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
题解:用二分法判断[0,x]之间的数的平方与x的关系。找到平法比x小的最大整数。
class Solution {
public:
int mySqrt(int x) {
int low = 0, high = x;
int ans = -1;
while(low <= high){
int mid = (high - low) / 2 + low;
if((long long) mid * mid <= x){
ans = mid;
low = mid + 1;
}
else high = mid - 1;
}
return ans;
}
};
367.有效的完全平方数
题目:给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。
题解:与上题相似,用二分法找到[0,num]之间平方等于num的数。
class Solution {
public:
bool isPerfectSquare(int num) {
int low = 0, high = num;
int ans = -1;
while(low <= high)
{
int mid = (high - low) / 2 + low;
if((long long) mid * mid < num)
low = mid + 1;
else if((long long) mid * mid > num)
high = mid - 1;
else return true;
}
return false;
}
};