启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情
该题是数组二分查找题型第四题。
题目来源
题目描述(简单)
给你一个非负整数 x ,计算并返回 x 的 算数平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
示例1
输入: x = 4
输出: 2
示例2
输入: x = 8
输出: 2
解释: 8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
提示
题目解析
该题有两种解题思路:
- 通过其它的数学函数代替平方根函数得到精确结果,取整数部分作为答案
- 通过数学方法得到近似结果,直接作为答案
该题作为二分查找的巩固训练,先采用二分查找的方法来得到结果。
二分查找
由于 x 的平方根整数部分是满足 的最大值 num ,因此我们对 num 进行二分查找即可。
于是,可以设定左边界为 0 ,右边界为 x ,在二分查找中,不断的比较 mid 和 x 的关系。
当你举例够多的时候,你会发现,在 范围中, 必定成立,所以可以将右边界设定为 。
代码
/**
* @param {number} x
* @return {number}
*/
var mySqrt = function(x) {
let left = 1, right = Math.ceil(x / 2)
while (right >= left) {
let mid = Math.floor((left + right) / 2)
if (mid * mid > x) {
right = mid - 1
}else if (mid * mid < x) {
left = mid + 1
}else{
return mid
}
}
return right
};
- 时间复杂度:。
- 空间复杂度:。
如图:
当等于和小于合并为一个判断条件时,你会发现, left - 1 一直是满足 num * num <= x 的最大值。
代码(精简代码)
/**
* @param {number} x
* @return {number}
*/
var mySqrt = function(x) {
let left = 1, right = Math.ceil(x / 2)
while (right >= left) {
let mid = Math.floor((left + right) / 2)
if (mid * mid > x) {
right = mid - 1
}else {
left = mid + 1
}
}
return left - 1
};
如图:
而当等于和大于合并时,你会发现 right 有两种情况:
right * right = x(right + 1) * (right + 1) = x
因此需要对 right 和 right + 1 进行判断。
代码(精简代码)
/**
* @param {number} x
* @return {number}
*/
var mySqrt = function(x) {
let left = 1, right = Math.ceil(x / 2)
while (right >= left) {
let mid = Math.floor((left + right) / 2)
if (mid * mid >= x) {
right = mid - 1
}else {
left = mid + 1
}
}
return (right + 1) * (right + 1) <= x ? right + 1 : right
};
如图:
总而言之,该题需要找到满足 num * num <= x 的最大值 num 。当 num * num === x 时,left - 1 和 right + 1 都可以作为返回结果。但是当 num * num < x 时,在最后一次循环 left === right 中,根据 mid * mid < x ,所以只会执行 left = mid + 1 该条语句,所以将等于的情况合并到小于的情况中时,返回结果不需要变化,但将等于的情况合并到大于的情况中时,返回结果就需要判断了。
袖珍计算器算法
该方法是一种用指数函数 exp ( Math.exp() - JavaScript )和对数函数 ln ( Math.log1p() - JavaScript )代替平方根函数的方法。通过有限的可以使用的数学函数,得到计算的预期结果。
公式如下:
注意:由于计算机无法存储浮点数的精确值(浮点数的存储方法可以参考 IEEE 754_百度百科) ,这里不在赘述。而指数函数和对数函数的参数和返回值均为浮点数,因此计算过程中会存在误差。例如当 x = 2147395600 时, 的运算结果与正确值 46340 相差 ,这样在对结果取整数部分时,会得到 46339 这个错误结果,因此得到整数部分,应当找出 res + 1 和 res 两个中哪一个是正确答案。
代码
/**
* @param {number} x
* @return {number}
*/
var mySqrt = function(x) {
if (x === 0) {
return 0
}
let res = Math.floor(Math.exp(0.5 * Math.log1p(x - 1)))
return (res + 1) * (res + 1) <= x ? res + 1 : res
};
- 时间复杂度:,由于内置的
exp函数与log函数一般都很快,所以这里将其复杂度视为 O(1) 。 - 空间复杂度:。
如图:
执行用时和内存消耗仅供参考,大家可以多提交几次。如有更好的想法,欢迎大家提出。