Q62- code69- x 的平方根 及其 变形

39 阅读3分钟

Q62- code69- x 的平方根

实现思路

1 二分查找法

  • 对于这种 极值边界极端情况,要么 提前收缩范围,要么 处理过程中 变乘法为除法
  • 开区间 + 求 <=的 最大值:一般是返回l
  • 开区间 + 求 >=的 最小值:一般是返回r

2 牛顿迭代法

S1 转化题意

  • 我们要找一个数r,使得 r^2 = t
  • 可以转化为 找方程 f(x)= x^2 - t = 0的解
  • 这就是说,我们要找到一个正实数x,使得 x^2 - t = 0

S2 理解导数

  • 对于函数 f(x)= x^2 - t
  • 它的导数 f'(x) = 2x
  • 导数表示函数在每个点的斜率

S3 在某一点做切线

  • 假设我们在点 x0处, 这个点的y0是 x0^2 - t
  • 这个点的 斜率/导数 是2x0

S4.1 写出 切线方程

  • y-yo = k(x - x0)
  • 即 y- (x0^2 - t) = 2x(x - x0)

S4.2 求解 正实数根,此时 y = 0

  • 即 0-(0^2 - t) = 2x0(x - x0) ==> -x0^2 + t = 2x0x - 2x0 ^ 2 ==> t = 2x0x - x0 ^ 2 ==> 2x0 x = x0 ^ 2 + t ==> x = (x0 ^ 2 + t) / 2x0 ==> x = (x0 + t/x0) / 2

S6 理解结果

  • 这个x 就是 切线与x轴的交点

  • 它比 原来的 x0更接近真实的平方根

  • 这就是我们迭代公式:x = (x0 + t/x0) / 2

  • 如果 x0正好是平方根,那么 t = x0 ^ 2

  • 代入公式:(x0 + t/x0)/2 = (x0 + x0) / 2 = x0

  • 说明如果找到了正确的值,这个值就不会再变化

参考文档

01- 方法1参考文档

02- 方法2参考文档

代码实现

1 方法1- 二分查找法

  • 时间复杂度:O(logn)
  • 空间复杂度:O(1)
function mySqrt(x: number): number {
  // 46340是 2^31 - 1 的平方根
  let l = 0, r = Math.min(x, 46340) + 1;
  while (l + 1 < r) {
    const mid = l + ((r - l) >> 1);
    mid * mid <= x ? (l = mid) : (r = mid);
  }
  return l;
}

2 方法2- 牛顿迭代法

  • 时间复杂度:O(logn)
  • 空间复杂度:O(1)
function mySqrt(x: number): number {
  if (x === 0 || x === 1) return x;
  // 选择更好的初始值:对于大数,可以用 x/2; 对于小数,可以用 x
  let diff = x > 4 ? x / 2 : x, t = x;
  // 易错点1: 循环条件应该是 diff * diff - t > 预期精度值
  while (Math.abs(diff * diff - t) > 0.00001) {
    diff = (diff + t / diff) / 2;
  }
  return ~~diff;
}

变形题目

1 求x的平方根,精确到小数点后n位

思路解析

1 为什么要 -(n + 1) 而不是 -n

  • 如果要求精确到小数点后 3 位
  • 那么误差必须小于 0.0001(比第3位更小一位)
  • 这样才能保证四舍五入后的结果是正确的

2 为什么 最后返回时需要 parseFloat(((l + r) / 2).toFixed(n))

  • 当循环结束时,说明 r - l ≤ eps(区间足够小了)
  • 此时 l 和 r 非常接近:l 通常会稍微小于真实值,r 会稍微大于真实值
  • 取 平均值 能得到最接近真实值的结果

代码实现

const mySqrt = (x, n) => {
  if (x === 0 || x === 1) return x;

  // 初始化左右边界, 优化右边界初始值
  let l = 0, r = x > 1 ? x : 1;

  // Math.pow(10, -1)  = 0.1
  // 计算精度:小数点后n位意味着误差要小于10^(-n-1)
  let eps = Math.pow(10, -(n + 1));

  // 二分搜索
  while (r - l > eps) {
    const mid = l + ((r - l) >> 1)
    mid * mid < x ? l = mid : r = mid;
  }
  
  // 取平均结果,减少误差
  // .toFixed(n)  保留指定小数位数(返回字符串)
  // parseFloat() 将字符串转换为浮点数
  return parseFloat(((l + r) / 2).toFixed(n));
};