本文始发于个人公众号:TechFlow,原创不易,求个关注

链接

Divide Two Integers

难度

Medium

描述

给定两个整数,被除数和除数,要求在不使用除号的情况下计算出两数的商

Given two integers dividend and divisor, divide two integers without using

multiplication, division and mod operator.

Return the quotient after dividing dividend by divisor.

The integer division should truncate toward zero.

样例 1:

Input: dividend = 10, divisor = 3
Output: 3

样例 2:

Input: dividend = 7, divisor = -3
Output: -2

注意:

  • 除数和被除数都在32位int的范围内
  • 除数不为0
  • 对于超界的情况返回\(2^{31}-1\)
  • Both dividend and divisor will be 32-bit signed integers.
  • The divisor will never be 0.
  • Assume we are dealing with an environment which could only store integers within the 32-bit signed integer range: [−\(2^{31}\), \(2^{31}\) − 1]. For the purpose of this problem, assume that your function returns \(2^{31}\) − 1 when the division result overflows.

题解

老规矩,我们依然从最简单的情况开始入手。我们都知道,在计算机内部,是二进制的,而二进制是只能进行加减的。所以没错,所有的乘法和除法的操作其实最终都会转换为加减法来进行。对于这道题而言也是一样的,既然禁止我们使用除法,那么我们可以用减法来代替。

暴力

最简单的策略就是我们可以用一个循环去不停地减,然后用一个累加器计算到底执行了多少次减法,当不够减的时候则停止。整个流程非常简单,但是我们还需要考虑一下正负号的问题,按照排列组合来看,被除数和除数一共有4中正负号的情况。但我们并不需要考虑那么多,这四种情况可以简单地归并成是否同号两种,然后进一步分析又会发现是否同号的计算过程并没有差别,唯一会影响的只有最后结果的正负号。

还有一点比较令人在意的是提示当中说的可能会超界的情况,我们来分析一下,其实会超界的可能性只有一个。那就是\(-2^{31}\)除以-1的情况,会得到\(2^{31}\),而32位int的正数范围最大是\(2^{31}-1\),所以我们需要在意这个问题。不过好在对于Python而言,int是没有范围的,所以可以忽略这个问题,只需要最后特判一下结果,但是对于C++和Java等语言而言,需要特判一下这个case。

我们来总结一下上面的过程,我们可以先将除数和被除数全部转化为正数,然后用一个标记flag来记录它们是否同号。再计算完结果之后,需要判断一下结果的范围是否越界,如果越界返回\(2^{31}-1\)。

代码如下:

class Solution:
def divide(self, dividend: int, divisor: int) -> int:
# 判断是否同号
flag = (dividend > 0 and divisor > 0) or (dividend < 0 and divisor < 0)
ret = 0
# 全部赋值为正
dividend, divisor = abs(dividend), abs(divisor) start = 0
# 模拟除法
while start + divisor <= dividend:
start += divisor
ret += 1 # 防止越界,注意只有正数才有可能越界
return min(ret, (1 << 31) - 1) if flag else -ret

这个代码当然是没有问题的,但是如果你真的这么提交,那么一定会超时。原因也很简单,当除数非常小,比如是1的时候,那么我们的循环次数就是被除数的大小。当我们给定一个很大的被除数的时候,会超时就是显然的了。

二进制优化

接下来讲的这个算法很重要,是快速幂等许多算法的基础,由于本题当中不是进行的幂运算,所以不能称作是快速幂算法,一时间也没有什么好的名字,由于本质上是通过二进制的运算来优化,所以就称为二进制优化吧。

在讲解算法之前,我们先来分析一下bad case出现的原因。之所以会超时,是因为有可能被除数非常小,比如是1,这个时候我们一位一位地减就非常耗时。那么很自然地可以想到,我们可不可以每次不是减去一个1,而是若干个1,最后把这些减去的数量加起来,是不是会比每次减去一个要更快一些呢?但是还有细节我们不清楚,我们怎么来确定这个每次应该减去的数量呢?如果确定了之后发现不够减,又应该怎么办呢?

要回答上面这个问题,需要对二进制有比较深入的理解。我们先把刚才的问题放一放,来看一看二进制对于除法的解释。举个简单的例子,比如15 / 3 = 5。我们知道15等于5个3相乘,那么我们把它们都写成二进制。15的二进制是1111,3的二进制是0011,5的二进制是0101,我们重点来看这个5的二进制。

5的二进制写成0101,展开的话会得到是\(2^2 + 2^0 = 4 + 1\),也就是说我们可以把15看成\((2^2+2^0)*3\),这个式子写成是\((0*2^3+1*2^2+0*2^1+1*2^0)*3=15\)。也就是说我们可以把被除数看成是若干个2的幂乘上除数的和,这就把一个除法问题转化成了加法问题。同样我们把求解商转化成了求解商的二进制表达,二进制表达有了,最后的商无非是再进行一个求和累加即可。

最后,我们来分析一下,为什么能够优化。因为题目当中已经限定了,除数和被除数都在32位的int范围。也就是说最多只有32个二进制位,那么我们的循环次数最多也就是32次。通过二进制优化,我们把原本一个\(O(n)\)的问题,降级成了\(O(\log n)\),这两者之间差了不止一个数量级,当然要快得多。

我们把上面这个思路写成代码,就可以得到答案:

class Solution:
def divide(self, dividend: int, divisor: int) -> int:
# 前面处理和之前一样
flag = (dividend > 0 and divisor > 0) or (dividend < 0 and divisor < 0)
ret = 0
dividend, divisor = abs(dividend), abs(divisor) # 预处理二进制数组
binary = [0 for _ in range(33)]
# 第0位即2的零次方乘上除数,所以就是除数本身
binary[0] = divisor
for i in range(1, 33):
# 后面每一位是前面一位的两倍,因为二进制
# << 是位运算左移操作,等价于*2,但是速度更快
binary[i] = binary[i-1] << 1 for i in range(32, -1, -1):
if binary[i] <= dividend:
dividend -= binary[i]
# 答案加上2^i
ret += (1 << i)
return min(ret, (1 << 31) - 1) if flag else -ret

这段代码不长,也没有用到什么特别牛哄哄的算法,无非是对二进制的一些运用。但是对于对二进制不够熟悉的初学者而言,想完全搞明白可能有些费劲,这也是很正常的。希望大家能够沉下心来好好理解,如果实在看不懂也没关系,在以后快速幂等算法当中,还会和它见面的。

今天的文章就是这些,如果觉得有所收获,请顺手扫码点个关注吧,你们的举手之劳对我来说很重要。

LeetCode29 Medium 不用除号实现快速除法的更多相关文章

  1. pom.xml配置文件配置jar(不用记,快速配置)

    1:网址:http://mvnrepository.com/ 2:在搜索栏搜索要用的框架;例如spring *以下为示例

  2. 层次关系表格,不用递归,快速检索。HierarchyId

    最近这几天写了个T4自动实现EF code first和Ado的存储过程.使用过程中发现了一个Sql的类型为HierarchyId.看到时真是百思不得齐姐.算了查一下MSDN吧.从微软官网找到了Hie ...

  3. C语言中的除法的计算

    不用除号,计算除法运算.思路是使用减法运算!思路1:循环采用减法每次减去n,直到做完减法之后结果小于0为止 但是这样次数较大  如求100/3,需要次数为34次. 思路2:循环采用减法每次减去k,K的 ...

  4. python学习笔记比较全

    注:本笔记基于python2.6而编辑,尽量的偏向3.x的语法 Python的特色 1.简单 2.易学 3.免费.开源 4.高层语言: 封装内存管理等 5.可移植性: 程序如果避免使用依赖于系统的特性 ...

  5. NYOJ 743

    复杂度 描述 for(i=1;i<=n;i++) for(j=i+1;j<=n;j++) for(k=j+1;k<=n;k++) operation; 你知道 operation 共 ...

  6. 【Cocos2d-x 3.X 资源及脚本解密】

    加密就不用说了,看上一篇2.X加密的方式,怎么弄都可以.的保证解密规则就行: 现在重点说3.X解密: 在新的3.X引擎中官方整合了大部分获取资源的方法,最终合成一个getdata: 可以从源码,和堆栈 ...

  7. OAuth2.0授权和SSO授权

    一. OAuth2.0授权和SSO授 1. OAuth2.0 --> 网页 --> 当前程序内授权 --> 输入账号密码 --> (自己需要获取到令牌, 自己处理逻辑) 授权成 ...

  8. Linux系统(二)软件的安装与卸载

    序言 上一篇我们了解啦Linux系统中,根目录下的各个文件夹是做什么用的啦,也学会文件如何压缩打包.那么接下来我们就该用到这个系统啦.用这个系统,就是用这个系统的软件,那么我们对我们需要的软件如何安装 ...

  9. MySql字符串函数使用技巧

    1.从左开始截取字符串 left(str, length) 说明:left(被截取字段,截取长度) 例:select left(content,200) as abstract from my_con ...

随机推荐

  1. HTTP1.1

    读了一本图解http,总结一下子. 1 .重要的头部   1.TCP/IP 通信传输流 五层模型 先盗个图,重点说明每过一层都会加个头,头很重要啊!其中https 就是在传输层搞事,把本来明文的数据包 ...

  2. cogs 3. 服务点设置 dijkstra

    3. 服务点设置 ★   输入文件:djsa.in   输出文件:djsa.out   简单对比时间限制:1 s   内存限制:128 MB [问题描述] 为了进一步普及九年义务教育,政府要在某乡镇建 ...

  3. Nginx代理服务——正向代理

    正向代理 在/opt/app/code的目录下创建一个joy.html文件 <html> <head> <meta charset="utf-8"&g ...

  4. java 语句流程

    一.if条件判断 太简单,不加说明 二.switch条件选择语句 注意: 1.switch(表达式),表达式的数据类型 byte/short/char/int/String/enum 2.别忘记写br ...

  5. 第一章001-003课程介绍、计算机网络概述、Internet概述

    计算机网络概述 课程安排: 第一章:概述 第二章:物理层 第三章:数据链路层 第四章:网络层 第五章:运输层 第六章:应用层 第七章:网络安全 第八章:因特网上的音频/视频服务 第九章:无线网络 第十 ...

  6. 快速构建第三方api应用

    1.使用框架和扩展 详细请看composer.json "php": "^7.1.3", "laravel-admin-ext/config" ...

  7. 面试系列-面试官:你能给我解释一下javascript中的this吗?

    一.前言 关于javascript中的this对象,可能已经被大家说烂了. 即使是这样,我依然决定将这篇文章给水出来.毕竟全国在新型肺炎的影响下,公司没法正常复工. 除了刷刷手机,还是要适当的学习一下 ...

  8. canvas跨域完美解决,微信头像解决跨域

    现在前端技术发展的越来越快,很多图片合成这种耗费服务器性能的,都可以移动到前端进行了合成了.而且合成很方便,我们利用 canvas 可以实现好多东西. 自动打算利用前端来合成图片,在网上就找到了 ht ...

  9. [Codeforces 1228E]Another Filling the Grid(组合数+容斥)

    题目链接 解题思路: 容斥一下好久可以得到式子 \(\sum_{i=0}^{n}\sum_{j=0}^{n}(-1)^{i+j}C_n^iC_n^j(k-1)^{ni+nj-ij}k^{n^2-(ni ...

  10. Mesh R-CNN 论文翻译(原理部分)

    毕设做Mesh R-CNN的实现,在此翻译一下原论文.原论文https://arxiv.org/pdf/1906.02739.pdf. 摘要 二维感知的快速发展使得系统能够准确地检测真实世界图像中的物 ...