【学习笔记】算法101--数学(二)2.3篇

前言:
学习《算法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)"

计算过程中有诸多细节问题需要注意:

  1. 分母denominator为 0 时,应当抛出异常,这里为了简单起见不考虑;

  2. 分子numerator为 0 时,结果为 0;

  3. 分母denominator 和 分子numerator中存在一个负数时,结果为负数。
    方法一:递归
    思路:
    维护一个记录余数的数组和记录余数第一次出现的位置的map,如果map中存在当前循环计算出的余数,则表示结果开始进入循环部分。
    详解:

  4. 先进行边界判断;

  5. 分别计算出整数和余数部分;

  6. 对余数部分使用长除法,计算出新的余数;

  7. 若map中存在新计算出的余数,则说明出现循环,合并计算结果;

  8. 若map中不存在新计算出的余数,则将计算出的余数记录下来;

  9. 重复第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}形式记录每一个余数出现的位置的哈希对象, 如果存在当前循环计算出的余数,则表示结果开始进入循环部分。
详解:

  1. 先进行边界判断;
  2. 分别计算出整数和余数部分;
  3. 对余数部分使用长除法计算出新的余数;
  4. 若remainders中存在新计算出的余数,则说明出现循环,合并计算结果;
  5. 若remainders中不存在新计算出的余数,则将计算出的余数和改余数出现的位置记录下来;
  6. 重复第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 。
详解:

  1. 对于任一给定数字x,进行循环;
  2. 对于每次循环的数字i ,将i*i与x的值进行比较;
  3. 如果前者大于后者,则说明 i 比我们需要的值大,又因为需要返回的是整数,所以,i - 1就是我们要找的值;
  4. 如果两者值正好相等,那么 i 就是我们要找的值;
  5. 如果前者小于后者,说明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]取中间值再判断。
详解:

  1. 设置3个变量分别为start、mid、end,并将start赋值为1,end赋值为所求目标x的一般的最正整数;
  2. 因为存在0 ,这样的特殊情况,开始时循环条件就不成立,直接返回end即可;
  3. 只要start不大于end,就进行循环操作,同时给mid赋值,这样把划分区间分为3部分[(start,mid),mid,(mid,end)];
  4. 计算mid与自身的乘积,并且与x进行比较,前者大于后者时,则需要为end重新赋值;
  5. 如果前者小于后者,需要更改最小值,则需要为start重新赋值;
  6. 这样不断地缩小取值的范围,如果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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值