2.搜索插入位置(35)
题目
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n)
的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums
为 无重复元素 的 升序 排列数组-104 <= target <= 104
思路及题解
-
解法一:关键是要分析出有四种返回值(插入位置),分别是:
- 在所有数字前面插入
- 找到需要的数字,返回该数字
- 在两个数字中间插入
- 在所有数字后面插入
分析可知,最后应该返回
high + 1
(有没有数学依据?)class Solution { public: int searchInsert(vector<int>& nums, int target) { int mid, low = 0, high = nums.size() - 1; while (low <= high) { mid = (low + high) / 2; if (nums[mid] > target) { high = mid - 1; } else if (nums[mid] < target) { low = mid + 1; } else if (nums[mid] == target) { return mid; } } return high + 1; } } ;
-
解法二:可视为找到无限接近目标值的数字的位置,在这个位置插入
需要注意ans的默认值为数组长度,因为如果应该在最后面插入时ans值不会被改变
class Solution { public: int searchInsert(vector<int>& nums, int target) { int mid,low=0,high=nums.size()-1,ans = nums.size(); while(low<=high){ mid = (low+high)/2; if(nums[mid]<target){ low = mid+1; } else if(nums[mid]>target){ ans = mid; high = mid -1; } else return mid; } return ans; } };
3.x的平方根(69)
题目
给你一个非负整数 x
,计算并返回 x
的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
**注意:**不允许使用任何内置指数函数和算符,例如 pow(x, 0.5)
或者 x ** 0.5
。
示例 1:
输入:x = 4
输出:2
示例 2:
输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
提示:
0 <= x <= 231 - 1
思路及题解
-
解法一:二分查找
-
假设其算术平方根是k,则有k2 ≥ x,用二分查找的方法,找到最近接近x的k2
-
若 mid 用 int 类型,计算 mid * mid 时会溢出,故计算时用长整型 long long
-
下面的代码是左闭右闭写法,故 low 、 high 都要 +1 、-1
-
ans 可保留离 x 最近的 k,即保留离 target 值最近的元素。如果 ans 和 low 放在一起,则得到比target 小的最近元素;如果 ans 和 high 放在一起,则得到比 target 大的最近元素,这一点很多二分查找的题目都会用到
class Solution { public: int mySqrt(int x) { int low = 0, high = x, ans = -1; while (low <= high) { int mid = (low+high)/2; // int mid = low + (high - low) / 2; if ((long long)mid * mid <= x) { ans = mid; low = mid + 1; } else { high = mid - 1; } } return ans; } };
以下代码为一开始写的二分查找,但是不知道在哪个地方出错,输入为 x =2147395600时,
输出为46339而不是正确的46340
class Solution { public: int mySqrt(int x) { double mid = x,low=0,high=x; while((mid*mid - x > 0.9) || (-(mid*mid - x) > 0.9) ){ mid = (low+high)/2; if((long long)mid*mid > x){ high = mid; } else if(mid*mid < x){ low = mid; } else return mid; } return (int)mid; } };
解法二:袖珍计算器
经过以下数学推导,可得到不用平方根函数的方法:
√x = x1/2 = (elnx)1/2 = e^1/2(ln x)^
注意: 由于计算机无法存储浮点数的精确值(浮点数的存储方法可以参考 IEEE 754,这里不再赘述),而指数函数和对数函数的参数和返回值均为浮点数,因此运算过程中会存在误差。例如当 x=2147395600 时,e1/2(lnx)的计算结果与正确值 46340 相差 10-11 ,这样在对结果取整数部分时,会得到 46339 这个错误的结果。
因此在得到结果的整数部分 ans 后,我们应当找出 ans 与 ans+1 中哪一个是真正的答案。
class Solution { public: int mySqrt(int x) { if (x == 0) { return 0; } int ans = exp(0.5 * log(x)); return ((long long)(ans + 1) * (ans + 1) <= x ? ans + 1 : ans); } };
4.有效的完全平方数
题目
给你一个正整数
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 不是一个整数。
提示:
1 <= num <= 231 - 1
-
思路及题解
本题可以借用上一题的二分查找,只需要在之前把ans的数据类型改为double(不丢失精度),最后再进行一次是否是完全平方数的判断即可
class Solution {
public:
bool isPerfectSquare(int num) {
int low = 0,high = num;
double ans = -1;
while(low <= high){
int mid = (low + high)/2;
if((long long)mid*mid <= num){
ans = mid;
low = mid + 1;
}
else{
high = mid - 1;
}
}
if(ans*ans == num) return true;
else return false;
}
};
5.在排序数组中查找元素的第一个和最后一个位置(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 元素没有出现,并且比所有元素小或比所有元素大。如 [ 2 , 2 ], target = 3;
- target 元素没有出现,大小在元素之间;
- target 元素出现
-
一次找到边界比较复杂,可以将复杂问题拆分,使复杂问题简单化。将找左、右边界作两个函数分别处理
-
容易联想到用 “ans” 变量来存储和 target 离的最近的元素作为它的边界
-
一开始rightBorder 和 leftBorder 的值都设为 -2,是为了方便处理情况一
如果是 nums = [2,2], target = 3 的情况,根据代码,leftBorder 就不会被赋值
-
为了方便情况三的判断(判断方式见代码)左右边界的值应该取不包含 target 的值
-
当 nums[mid] == target 时,也要更新一次 rightBorder/ leftBorder,是为了取到不包含 target 的边界值
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int rightBorder = getRightBorder(nums,target);
int leftBorder = getLeftBorder(nums,target);
//情况一
if(rightBorder == -2 || leftBorder == -2) return {-1,-1};
//情况二
else if(rightBorder - leftBorder > 1) return{leftBorder + 1, rightBorder - 1};
//情况三
else return {-1,-1};
}
public:
int getRightBorder(vector<int>& nums, int target){
int left = 0, right = nums.size() - 1, mid;
int rightBorder = -2; //没有找到时值为-2
while(left <= right){
mid = left+(right - left)/2;
if(nums[mid]<=target){
// 当nums[middle] == target的时候,更新left,这样才能得到target的右边界
left = mid + 1;
rightBorder = left;
}
else{
right = mid - 1;
}
}
return rightBorder;
}
int getLeftBorder(vector<int>& nums, int target){
int left = 0, right = nums.size() - 1, mid;
int leftBorder = -2; //没有找到时值为-2
while(left <= right){
mid = left+(right - left)/2;
if(nums[mid]<target){
left = mid + 1;
}
else{ // 寻找左边界,就要在nums[middle] == target的时候更新right
right = mid - 1;
leftBorder = right;
}
}
return leftBorder;
}
};