704、二分查找
题目介绍:
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
提示:
你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。
思路:
此题前提为升序数组且元素都是不重复的,故可以使用二分法,但二分法又可分为两种:左闭右闭及左闭右开。两种写法的主要注意事项为边界的确定:
左闭右闭即:[left , right ] , 判断条件应为while(left <= right) ,边界移动为 right = mid -1 , 下面为具体代码:
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 左闭右闭
while (left <= right) {
int middle = left + (right - left) / 2; // 取中间下标
if (nums[middle] < target) { // 目标在右侧
left = middle + 1; // 右侧下标移动
} else if (nums[middle] > target) { // 目标在左侧
right = middle - 1; // 左侧下标移动
} else {
return middle;
}
}
return -1;
}
};
左闭右开即:[left , right ) , 判断条件应为while(left < right) ,边界移动为 right = mid , 下面为具体代码:
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() ; // 左闭右开
while (left < right) {
int middle = left + (right - left) / 2; // 取中间下标
if (nums[middle] < target) { // 目标在右侧
left = middle + 1; // 左侧下标移动
} else if (nums[middle] > target) { // 目标在左侧
right = middle ; // 右侧下标移动
} else {
return middle;
}
}
return -1;
}
};
35. 搜索插入位置
题目介绍:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums 为 无重复元素 的 升序 排列数组
-104 <= target <= 104
思路:
此题依旧是提供升序数组及无重复元素,故依旧可以使用二分法,但与上体不同的是需要处理目标值不存在于数组中时返回应该插入的位置。所以在每次判断中间下标mid的元素是否大于目标值,若大于目标值则需要记录mid的值,若等于目标值则退出循环,最后返回下标值。
下面是左闭右闭的写法,具体代码如下:
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0 , right = nums.size()-1 , index = nums.size() ;
while( left <= right ){
int mid = (right - left ) / 2 + left ; //防止值超过int范围溢出
if(target > nums[mid] )
left = mid +1 ;
else if( target < nums[mid] ){
index = mid ; //记录下当前的下标值,若循环至最后,还未找到目标值,则最后一次所记录的下标值就是需要插入的位置
right = mid -1 ; // target 在左区间,所以[left, middle - 1]
}else{
index = mid ;
break ;
}
}
return index ;
}
};
34. 在排序数组中查找元素的第一个和最后一个位置
题目介绍:
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例3:
输入:nums = [], target = 0
输出:[-1,-1]
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109
思路:
本题为二分法的进阶使用。数组中存在重复元素,我们要找到target在数组中出现的左右区间,所以就可以分为三种情况:
1、target不在数组范围内,例数组为[5,6,7,8],target的值为2或者9。这时应该返回[-1,-1]
2、target在数组范围内,但数组中并不包含此元素,例数组为[2,6,8,11],target的值为9,这种情况得返回值亦为[-1,-1]
3、target 在数组范围中,且数组中存在target,例如数组{3,4,5,6,7},target为6,此时应该返回[3, 3]
然后再通过两次二分法的使用来确定target的左右边界。
具体代码如下:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int rightIndex = getRight( nums , target) ;
int leftIndex = getLeft(nums , target ) ;
if( rightIndex == -2 || leftIndex == -2 ) return { -1 ,-1 } ; //表明target 在数组范围的右边或者左边
//因为左右边界都指的是不包含target的,故返回的值中左边界应+1,有边界-1,才是target出现的首末下标位置
if( (rightIndex - leftIndex) > 1 ) return { leftIndex+1 , rightIndex-1} ; //当数组中存在target的时候,左边界与右边界之差一定大于1
return {-1,-1}; //表示target在数组的范围内,但数组中不包含此数
}
//获取不包含target的右边界,例target为6,数组为[4,6,6,8,9],右边界为3
//当返回值为-2时,表明target不在数组的范围内且在数组的左端
int getRight(vector<int>& nums, int target){
int left = 0 , right = nums.size() -1 , index = -2 ; //将下标的默认值定为-2,方便后续对各种情况进行判断
while( left <= right ){
int mid = (right-left)/2 +left ; //防溢出
if( nums[mid] > target )
right = mid - 1 ;
else{
//当nums[mid] == target的时候依然更新left的值,这样才可以取到不包含target的右边界
left = mid + 1 ;
index = left ;
}
}
return index ;
}
//获取不包含target的左边界,例target为6,数组为[4,6,6,8,9],右边界为0
//当返回值为-2时,表明target不在数组的范围内且在数组的右端
int getLeft(vector<int>& nums, int target){
int left = 0 , right = nums.size() -1 , index = -2 ;
while( left <= right ){
int mid = (right-left)/2 +left ; //防溢出
if( nums[mid] >= target ){
//当nums[mid] == target的时候依然更新right的值,这样才可以取到不包含target的左边界
right = mid - 1 ;
index = right ;
}else{
left = mid + 1 ;
}
}
return index ;
}
};
69. x 的平方根
题目介绍:
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
示例 1:
输入:x = 4
输出:2
示例 2:
输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
提示:
0 <= x <= 2^31 - 1
思路:
此题依然可以用二分法解决,left为0,right为x 。当x的平方根为整数时,返回的值是midmid == x的mid值,当x的平方根不为正数的时候就应取左后一次midmid < x的mid值。
具体代码如下:
class Solution {
public:
int mySqrt(int x) {
//使用二分查找法
int left = 0 , right = x , result = 0 ;
while( left <= right ){
int mid = left + (right-left)/2 ; //防止超出int数值范围溢出
if( (long long ) mid*mid > x ){ //强转mid*mid乘积为long long类型防止溢出
right = mid -1 ;
}else{
//当x的平方根不为整数的时候,获取的值应是最后一次mid*mid < x的值
result = mid ;
left = mid + 1 ;
}
}
return result ;
}
};
367. 有效的完全平方数
题目介绍:
给你一个正整数 num 。如果 num 是一个完全平方数,则返回 true ,否则返回 false 。
完全平方数 是一个可以写成某个整数的平方的整数。换句话说,它可以写成某个整数和自身的乘积。
不能使用任何内置的库函数,如 sqrt 。
示例 1:
输入:num = 16
输出:true
解释:返回 true ,因为 4 * 4 = 16 且 4 是一个整数。
示例 2:
输入:num = 14
输出:false
解释:返回 false ,因为 3.742 * 3.742 = 14 但 3.742 不是一个整数。
提示:
0 <= x <= 2^31 - 1
思路:
二分法的简单使用,left为0,right为 num+1 ,当mid*mid == num 时返回true,需要注意数值溢出问题。
具体代码如下:
class Solution {
public:
bool isPerfectSquare(int num) {
//二分查找
int left = 0 ;
long long right = (long long )num +1 ;
while( left < right ){
int mid = left + (right - left ) /2 ;
if( (long long )mid*mid > num )
right = mid ;
else if( (long long )mid*mid < num )
left = mid + 1 ;
else
return true ;
}
return false ;
}
};