当前位置: 首页 > news >正文

Leetcode 69——不使用sqrt函数情况下求平方根整数部分(暴力求解法和二分查找法)

文章目录

  • 不使用sqrt函数情况下求平方根整数部分
    • 暴力求解
      • 解题思路
      • 代码实现
        • int的上溢出风险
        • 利用不等式性质调整边界
        • 强制类型转换到更长字节的整形
      • 时间复杂度和空间复杂度
    • 二分查找
      • 算法思路
      • 代码实现
      • 时间复杂度和空间复杂度

不使用sqrt函数情况下求平方根整数部分

本篇文章是对Leetcode 69题目进行记录

现在提供以下两种思路:

  1. 暴力求解
  2. 二分查找

以下我们将对这两种算法进行记录,原题链接:
Leetcode 69.x的平方根

暴力求解

几乎所有的题,我们第一时间都想到的是能不能暴力求解,先不管时间复杂度的要求。当然这题是没有要求的,我们可以先试着写一下暴力求解的思路。

解题思路

因为只需要求的是整数x(int 类型)的平方根的整数部分,使用暴力求解是非常简单的。定义int i = 1;只需要让i从1开始遍历到x,i2只要小于等于x就继续往下走,直到大于的时候返回i - 1这个值即可。思路十分简单。

代码实现

int的上溢出风险
class Solution {
public:
    int mySqrt(int x) {
        if (!x)
            return x;
        int sqrtx = 1;//专门用来接收平方根的整数部分
        for (int i = 1; i <= x; i++) {
            if (i * i <= x)
                sqrtx = i;
            else
                break;
        }
        return sqrtx;
    }
};

很多人会不假思索地写出这么一段代码。但是忽略了一个问题就是上溢出的风险。

对于int类型,其实是unsigned int类型,是有符号整形,最大值应该是231 - 1 = 2147483647才对。当i从1开始遍历,一开始i比较小,那么i2的值肯定是可以用int接收的(本身i * i都是int类型,算出来也是int类型)。

但是我们尝试着跑一下这个代码:
在这里插入图片描述
执行是出错的,看到错误原因是当i = 46341的时候,i * i的值是无法作为int类型数据和比较的,所以得进行修改代码。

利用不等式性质调整边界

通过计算器计算得知:46431 * 46431 = 2155837761是大于int类型数据的最大值的。也就是说,对于x来讲,无论多大,只要是再int范围内,最多i只需要遍历到46431就会停下,只不过这个时候会导致上溢出风险。

我们学过不等式:

if a < b
then a * c < b * c (c != 0)

既然只有i = 46431的时候会导致平方越界,那么两边同时乘一个0.5不就好了,这样子还会把计算后的结构类型提升到double类型,就可以比较了,一定不会越界:

class Solution {
public:
    int mySqrt(int x) {
        if (!x)
            return x;
        int sqrtx = 1;//专门用来接收平方根的整数部分
        for (int i = 1; i <= 0.5 * x; i++) {
            if (0.5 * i * i <= 0.5 * x)
                sqrtx = i;
            else
                break;
        }
        return sqrtx;
    }
};
强制类型转换到更长字节的整形

这个前提是题目没有要求说题目中不给使用64位的整形数据。

既然int比较不了,那直接类型提升到long long不就可以了吗?

class Solution {
public:
    int mySqrt(int x) {
        if (!x)
            return x;
        int sqrtx = 1;
        for (int i = 1; i <= 0.5 * x; i++) {
            if ((long long)i * i <= x)
                sqrtx = i;
            else
                break;
        }
        return sqrtx;
    }
};

时间复杂度和空间复杂度

在这里插入图片描述
我们看看经过修改后的代码(调整边界方法时间和这个差不多)

时间复杂度为O(LogX),其实很好理解,如果x比较大,那么跑的次数会非常接近刚刚的那个数46431,其实也就是x的平方根整数部分,那效率还是比较低的。

空间复杂度为O(1),因为该算法没有另外开辟空间。

二分查找

既然暴力求解是从1开始找到x,那么就是在一个有序的序列上找一个确定的数,那么我们可以尝试一下使用二分查找。

算法思路

我们可以尝试一下二分查找。刚刚我们也知道int类型数据内找平方根最大也就到46431就会越界,所以我们可以控制一下查找的范围,就定在1~50000即可。

二分查找就是在1~50000中查找:
定义一个变量上界为int up ,下届为int down。设中间数为int mid

mid = (up + down) / 2。开始查找:
如果当前mid * mid的值和x相同,那就返回 mid这个值。
如果小于就让mid为下界,即赋值给down,重新计算mid
如果大于就让mid为上界,即赋值给up,重新计算mid

但是不是所有的数都能找到整数平方根,但是二分查找会不断压缩直到只有一个数,此时mid的值和down是一样的,那就直接停止循环即可。

但是每个数字都从50000开始为上界还是太慢了。就比如我要找10的平方根,从10开始找才是更快的,从50000开始为上界那就不太行了。但是int所有类型的数据最大的平方根也不超过50000,所以我们做一个规定,如果传入的x小于等于50000,就让上界为x,反之为50000。

我们以60作为例子:
在这里插入图片描述
很明显,这个思路是可行的。现在还有一个问题就是对于0这个数是否也能用这个逻辑呢?

如果x == 0,上界为0,下界为1,那么中间数就是0。然后当前中间数偏小,就让0为下届,原本的上界0仍是上界,再计算此时的mid也是0。还是偏小。但是此时middown的值相同,直接退出循环就可以了。所以也是可以成功的。

代码实现

class Solution {
public:
    int mySqrt(int x) {
        // 此时mid大约为2.9w
        int up = (x <= 50000) ? x : 50000, down = 1;
        int mid = (up + down) / 2;
        while (mid != down) {
            // 乘0.5是防止越界int的范围
            if (0.5 * mid * mid == 0.5 * x)
                return mid;
            else if (0.5 * mid * mid > 0.5 * x)
                up = mid;
            else
                down = mid;
            mid = (up + down) / 2;
        }
        return mid;
    }
};

当然还是需要注意上溢出风险的。具体处理方法可以参照上个部分讲的两种解决方案。

时间复杂度和空间复杂度

很明显空间复杂度仍是O(1),因为没有另外开辟空间进行操作。
时间复杂度仍为O(LogX),这个看着和上面那个算法的O(LogX)好像没什么区别,实则不然。

虽然二者查找次数均为LogX次,但是也是有区别的。特别数字比较大的时候。当数字较大,暴力查找是要从1到x的平方根跑满的,而二分查找却不用那么多次。

就比如找90000的平方根,暴力查找要找300次,而二分查找只需要14次(大致计算),这个效率差是非常大的。

http://www.dtcms.com/a/123061.html

相关文章:

  • Pytest多环境切换实战:测试框架配置的最佳实践!
  • EPGAN:融合高效注意力的生成对抗网络图像修复算法
  • Promise(微任务)和setTimeout(宏任务)的理解
  • P1825 [USACO11OPEN] Corn Maze S【java】【AC代码】
  • C++:函数模板类模板
  • 什么是巨型帧Jumbo Frames?云服务器开启巨型帧有什么用?
  • 2025年4月9日-华为暑期实习-第二题-200分
  • 动态类生成 / 加载机制(更新)
  • Java集合框架:核心接口与关系全解析
  • springboot中测试python脚本:ProcessBuilder
  • 如何使用 PyTorch 和 EfficientNet 创建一个 AI 游戏外挂[特殊字符]
  • 基于MCP协议调用的大模型agent开发03
  • InnoDB 事务更新过程深度剖析:原理、流程与优化
  • 前端-vue2核心
  • 如何用postman做接口自动化测试?
  • Seata框架,如何保证事务的隔离性?
  • 【时时三省】(C语言基础)用switch语句实现多分支选择结构
  • 【运维 | 硬件】服务器中常见的存储插槽类型、对应的传输协议及其特性总结
  • 环信鸿蒙版 UIKit 快速上手指南
  • 如何将多个Word文档合并
  • 【HTML】动态背景效果前端页面
  • 基于 Qt4 的图片处理工具开发(二):增加对比度调节、界面布局优化、多线程操作
  • 初识Redis · 简单理解Redis
  • 使用注解@RequestBody变红的解决问题
  • 【spark认任务提交】配置优先级顺序
  • 银河麒麟v10(arm架构)部署Embedding模型bge-m3【简单版本】
  • 【C++进阶】关联容器:multimap类型
  • 学习海康VisionMaster之四边形查找
  • 【达梦数据库】bash: /dev/null: Permission denied
  • 使用stm32cubeide stm32f407 lan8720a freertos lwip 实现tcp客户端、服务端及网络数据转串口数据过程详解