[JavaScript 刷题] 二分搜索 - 求开方,Leetcode 69

Posted GoldenaArcher

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[JavaScript 刷题] 二分搜索 - 求开方,Leetcode 69相关的知识,希望对你有一定的参考价值。

[javascript 刷题] 二分搜索 - 求开方,Leetcode 69

题目地址:69. Sqrt(x)

题目

如下:

Given a non-negative integer x, compute and return the square root of x.

Since the return type is an integer, the decimal digits are truncated, and only the integer part of the result is returned.

Note: You are not allowed to use any built-in exponent function or operator, such as pow(x, 0.5) or x ** 0.5.

解题思路

依旧日常开始捋题,这道题不算长,而且重点要么加粗了,要么就用斜线标明了,题目含义还算清晰。

  1. 首先提供的数字,也就是 input x 是一个非负整数

  2. 所需的返回值是 x 的平方根,即,当输入值为 4 时,返回值是 2

  3. 小数点后的数字会被 压缩,只有 整数 部分的结果会被返回

    这一部分显然是描述了 非完全平方数 的平方根的返回值。以 8 为例, 8 \\sqrt{8} 8 的值是 2.82842712... 2.82842712... 2.82842712...,这种情况下小数点后的数字可以省略,只需要返回 2 2 2 即可。

  4. 不能使用 JavaScript 内置的函数或操作符,例如说 Math.pow(), 或 num ** 0.5

  5. 根据题目类型提示,这道题可以用 二分搜索 解决

使用 JavaScript 解题

这道题还是挺简单的,直接写就好了

最初的解法

/**
 * @param {number} x
 * @return {number}
 */
var mySqrt = function (x) {
  let lo = 0,
    hi = x;
  // 将 mid 放在外面,可以在循环体内少创建一些变量
  let mid;
  while (lo <= hi) {
    // JavaScript 的数字类型是 Number,而 Number 不分 整型 和 浮点,所以需要向下取整
    mid = Math.floor((lo + hi) / 2);
    // 可以不设置变量,这里设置只是为了方便点
    const squaredVal = mid * mid;
    if (squaredVal === x) return mid;
    else if (squaredVal > x) {
      hi = mid - 1;
    } else {
      lo = mid + 1;
    }
  }
  // 注意这个时候的 hi < lo
  // 因为循环条件是 lo <= hi
  // 当跳出循环了,就代表 hi < lo,否则会一直在循环体内循环
  return hi;
};

但是这个时候我发现运行时间大概在 128-148 ms 之间,只超过了 10-20% 左右的成绩。之前也曾经提过,大概是在 50-60% 证明这个算法是比较正常(不搞极端的边界条件)的解,那么只超过 10-20% 就证明还有能够提高的地方。

简单的优化

在不瞎折腾边界条件的情况下,比较能够节省运行时间的方式就是设置上限了,最初的上限是设置成了 hi = x;,这一点可以进行一些优化。

从图像上来说, y = x 2 y = x^2 y=x2 y = 2 x y = 2x y=2x 的交点在 x = 2 x = 2 x=2 上,随后 y = x 2 y = x^2 y=x2 的增长速度就远远的超过了 y = 2 x y = 2x y=2x,于是,这也可以将检查条件分为以下几种情况:

  1. x ≤ 0 x \\leq 0 x0,直接返回 error

    题目中提到的是 Given a non-negative integer,所以在负值的情况下,输入值就是有问题的值,这是比较基本的检查条件

  2. ( x 2 ) 2 = x (\\frac{x}{2})^2 = x (2x)2=x 时,直接返回 x 2 \\frac{x}{2} 2x

    这种情况其实也只存在于 0 和 2,如图所示,这是两个函数的交点

  3. 其他情况下,将 hi 的值设为 x 2 + 1 \\frac{x}{2} + 1 2x+1

    之所以设置为 x 2 + 1 \\frac{x}{2} + 1 2x+1 而不是 x 2 \\frac{x}{2} 2x 是因为 mid 会向下取整,直接设置 x 2 \\frac{x}{2} 2x 会导致当输入值为 3 的时候计算错误

    这一步就相当于直接减少了一次折半的操作,理论上来说是能够稍微提升一点速度的。

JavaScript 的实现为:

/**
 * @param {number} x
 * @return {number}
 */
var mySqrt = function (x) {
  if (x < 0) return new Error('x cannot be smaller than 0');
  if ((x / 2) * (x / 2) === x) return x / 2;
  let lo = 1,
    hi = x / 2 + 1;
  while (lo <= hi) {
    const mid = Math.floor((lo + hi) / 2);
    const squaredVal = mid * mid;
    if (squaredVal === x) return mid;
    else if (squaredVal > x) {
      hi = mid - 1;
    } else {
      lo = mid + 1;
    }
  }
  // hi is smaller than lo
  return hi;
};

跟其他几次对比起来,的确有着比较显著的提速效果:

当然,本质上来说时间复杂度还是 O ( log ⁡ ( n ) ) O(\\log(n)) O(log(n)),这点并不会有特别大的差别,只是成绩看起来稍微没有这么惨,感觉会好一点……

以上是关于[JavaScript 刷题] 二分搜索 - 求开方,Leetcode 69的主要内容,如果未能解决你的问题,请参考以下文章

[JavaScript 刷题] 二分搜索 - 求开方,Leetcode 69

[JavaScript 刷题] 二分搜索 - 第一个错误的版本,Leetcode 278

[JavaScript 刷题] 二分搜索 - 大于给定元素的最小元素,Leetcode 744

[JavaScript 刷题] 二分搜索 - 有序数组的 Single Element,Leetcode 540

求开散列表的平均查找长度

leetcode刷题记录——二分简单题