前言:
学习《算法101》,每天进步一点点,加油💪。Pow(x,n)、两数相除、分数到小数和x的平方和。
一、pow(x,n)
实现pow(x,n),即计算x的n次幂函数。
示例1:
输入: 2.00000, 10
输出: 1024.00000
示例2:
输入: 2.10000, 3
输出: 9.26100
示例3:
输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25
方法一:二分法
思路
看到题目首先想到可以用暴力计算,如果n为整数,则做n次底数x的累乘,如果n为负数,则做n次底数(1/x)的累乘,于是有了如下代码:
function myPow (x,n) {
//考虑n为0的边界情况
if (n === 0) {
return 1;
} else if (n === 1) {
return x;
} else if (n === -1) {
return 1 / x;
}
const base = n > 0 ? x:1 /x; //通过正负号,确认参与幂运算的底数
let half = parseInt( n / 2,10); //将n的值缩小一半
const result = myPow(x,half); //保存折半计算的值,避免重复计算
if (n % 2) { //如果n是奇数,则需要额外乘以一次底数
return base * result * result;
}
//如果n是偶数,则直接返回折半计算的乘积
return result * result;
};
方法二:快速幂
思路
继续在指数n上做文章,将n看做数列之和,使得n = a₁ + a₂ + … ;
function myPow (x,n) {
if (n === 0) {
return 1;
} else if(n === 1) {
return x;
} else if (n === -1) {
return 1/x;
}
let pow = Math.abs(n); //取幂值绝对值,防止-1向右移位结果永远是-1的情况
let result = 1; //计算的最终结果
let base = x; //初始值为x^1
while (pow) {
if (pow & 1 === 1) { //判断当前位是0 还是1
result = result * base;
}
base *= base; //更新第n位上的权重
pow = pow >> 1; //向右位移
}
return n > 0 ? result : 1 / result;
};
二、两数相除
给定两个整数,被除数dividend和除数divisor。将两数相除,要求不使用乘法、除法和mod运算符。
返回被除数 dividend 除以除数 divisor 得到的商。
示例:
输入: dividend = 10, divisor = 3
输出: 3
示例 2:
输入: dividend = 7, divisor = -3
输出: -2
说明:
被除数和除数均为 32 位有符号整数。 除数不为 0。 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−231, 231 − 1]。本题中,如果除法结果溢出,则返回 231 − 1。
方法一:利用累加的方式寻找商
思路:
我们先让除数 divisor 左移直到大于被除数之前得到一个最大的n的值,说明被除数dividend至少包含 2ⁿ个 divisor ,然后减去这个数,再一次找到多少个n-1、n-2 。
详解:
/**
* @param {number} dividend
* @param {number} divisor
* @return {number}
*/
const divide = function (dividend, divisor) {
const MIN_VALUE = -2147483648;
const MAX_VALUE = 2147483647;
const positive = (dividend ^ divisor) >= 0;
let d = Math.abs(dividend);
const b = Math.abs(divisor);
let res = 0;
while (d >= b) {
let tmp = b;
let p = 1;
// 寻找有多少个 b
while (d >= tmp << 1 && tmp < 1073741823) { // 1073741823 考虑溢出的情况
tmp <<= 1;
p <<= 1;
}
d -= tmp;
res += p;
}
if (positive) {
return res > MAX_VALUE ? MAX_VALUE : res;
}
return res < MIN_VALUE ? MIN_VALUE : -res;
};
三、分数到小数
给定两个整数,分别表示分数的分子 numerator 和分母 denominator,以字符串形式返回小数。
如果小数部分为循环小数,则将循环的部分括在括号内。
示例:
输入: numerator = 1, denominator = 2
输出: "0.5"
输入: numerator = 2, denominator = 3
输出: "0.(6)"
计算过程中有诸多细节问题需要注意:
-
分母denominator为 0 时,应当抛出异常,这里为了简单起见不考虑;
-
分子numerator为 0 时,结果为 0;
-
分母denominator 和 分子numerator中存在一个负数时,结果为负数。
方法一:递归
思路:
维护一个记录余数的数组和记录余数第一次出现的位置的map,如果map中存在当前循环计算出的余数,则表示结果开始进入循环部分。
详解: -
先进行边界判断;
-
分别计算出整数和余数部分;
-
对余数部分使用长除法,计算出新的余数;
-
若map中存在新计算出的余数,则说明出现循环,合并计算结果;
-
若map中不存在新计算出的余数,则将计算出的余数记录下来;
-
重复第3步。
const fun = (map, remainder, remainders, denominator) => {
if (!remainder) {
return remainders;
}
let num = 0;
if (map.has(remainder)) {
remainders.splice(map.get(remainder), 0, '(');
remainders.push(')');
return remainders;
} else {
map.set(remainder, remainders.length);
remainder *= 10;
num = Math.floor(remainder / denominator);
remainder %= denominator;
remainders.push(num);
return fun(map, remainder, remainders, denominator);
}
};
const fractionToDecimal = function (numerator, denominator) {
// 判断边界
if (denominator === 0) {
return '';
}
// 判断边界
if (numerator === 0) {
return '0';
}
let result = '';
// 只存在1位负数
if ((denominator < 0) ^ (numerator < 0)) {
result += '-';
denominator = Math.abs(denominator);
numerator = Math.abs(numerator);
}
// 整数部分
const integer = Math.floor(numerator / denominator);
result += integer;
// 余数部分
const remainder = numerator % denominator;
if (remainder) {
result += '.';
}
let remainders = [];
const map = new Map();
if (remainder) {
remainders = fun(map, remainder, remainders, denominator);
}
result += remainders.join('');
return result;
};
方法二:迭代
思路:
维护一个记录以{remainder: position}形式记录每一个余数出现的位置的哈希对象, 如果存在当前循环计算出的余数,则表示结果开始进入循环部分。
详解:
- 先进行边界判断;
- 分别计算出整数和余数部分;
- 对余数部分使用长除法计算出新的余数;
- 若remainders中存在新计算出的余数,则说明出现循环,合并计算结果;
- 若remainders中不存在新计算出的余数,则将计算出的余数和改余数出现的位置记录下来;
- 重复第3步。
const fractionToDecimal = function (numerator, denominator) {
// 判断边界
if (denominator === 0) {
return '';
}
// 判断边界
if (numerator === 0) {
return '0';
}
let result = '';
// 只存在1位负数
if ((denominator < 0) ^ (numerator < 0)) {
result += '-';
denominator = Math.abs(denominator);
numerator = Math.abs(numerator);
}
// 整数部分
const integer = Math.floor(numerator / denominator);
result += integer;
// 余数部分
let remainder = numerator % denominator;
if (remainder) {
result += '.';
}
// 小数部分
let decimal = '';
let index = 0;
const remainders = {};
while (remainder) {
const target = remainders[remainder];
if (!isNaN(target)) {
decimal = `${decimal.substring(0, target)}(${decimal.substring(target)})`;
break;
}
remainders[remainder] = index++;
remainder *= 10;
const num = Math.floor(remainder / denominator);
decimal = `${decimal}${num}`;
remainder = remainder % denominator;
}
result += decimal;
return result;
};
四、x的平方根
实现int sqrt(int x)函数。即计算并返回x的平方根,其中x是非负整数,由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例1:
输入: 4
输出: 2
示例2:
输入: 8
输出: 2
解释: 8 的平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
方法一:顺序查找
思路:
从数字1开始找,一旦找到平方值等于x的数字i,直接返回i 。如果找到平方值大于x的数字 i ,需要返回 i - 1 。
详解:
- 对于任一给定数字x,进行循环;
- 对于每次循环的数字i ,将i*i与x的值进行比较;
- 如果前者大于后者,则说明 i 比我们需要的值大,又因为需要返回的是整数,所以,i - 1就是我们要找的值;
- 如果两者值正好相等,那么 i 就是我们要找的值;
- 如果前者小于后者,说明i与自身的乘积还没有达到要求,需要继续循环下去。
const mySqrt = function(x) {
for (let i = 1;i <= x; i++) {
if (i*i > x) {
return (i - 1);
}else if(i*i == x) {
return i;
}
}
return 0;
}
方法一:二分查找法
思路:
采用二分查找的思想,因为x是非负整数,那么当x是0的时候平方根为0,x为1时平方根为1,只有当x大于1时才需要计算。因为当 x>1时,x 的平方根 y 肯定在 1到x 之间 即 1< y < x,知道了数值的范围, 我们可以每次把划分区间分为3部分 [(0,mid),mid,(mid,end)],从而不断缩小空间,即:在区间[0,end]取中间值mid,判断 mid * mid 与 x的大小关系 。
当 mid mid > x 时 表示, mid mid 大了, 那么接下来在[1, mid-1]取中间值再判断。
当 mid mid < x时 表示, mid mid 小了, 那么接下来在[mid +1,x]取中间值再判断。
详解:
- 设置3个变量分别为start、mid、end,并将start赋值为1,end赋值为所求目标x的一般的最正整数;
- 因为存在0 ,这样的特殊情况,开始时循环条件就不成立,直接返回end即可;
- 只要start不大于end,就进行循环操作,同时给mid赋值,这样把划分区间分为3部分[(start,mid),mid,(mid,end)];
- 计算mid与自身的乘积,并且与x进行比较,前者大于后者时,则需要为end重新赋值;
- 如果前者小于后者,需要更改最小值,则需要为start重新赋值;
- 这样不断地缩小取值的范围,如果mid乘积与x两者相同,mid值就是我们需要的值。
代码:
const mySqrt = function(x) {
let start = 1;
let end = Math.floor(x/2) + 1;
let mid;
while(start <= end) {
mid = Math.floor((start + end) / 2);
if (mid * mid > x) {
//更改最大值,继续取中间值
end = mid - 1
}else if(mid * mid < x){
//更改最小值,继续取中间值
start = mid + 1
} else {
return mid
}
}
return end
};
参考博客: 《面试助力,算法101:JavaScript描述》 https://101.zoo.team/shu-xue/shu-xue-part-23-de-mi-excel-biao-lie-xu-hao-kuai-le-shu-he-jie-cheng-hou-de-ling