难度较高,阅读时间大概 28 分钟

这是数论的第二篇,在《素数筛法》中,我们重温了素数这个数学定义,并且给出了区别于教科书上更高效的 Eratosthenes 筛法欧拉线性筛。这篇文会从 GCD 问题出发,一起来探究一下扩展欧几里得算法。

看了标题,也许你有疑问,什么是欧几里得算法?欧几里得算法是为了解决 GCD 问题,这里的 GCD 是指 Greatest Common Divisor 即 最大公约数,而不是 iOS 中的 Grand Central Dispatch ? 。所以这篇分享是关于算法的。

欧几里得算法(GCD)

求 GCD 在数论中公认的最常用算法即为欧几里得算法,也就是我们在高中时学到的辗转相除法

欧几里得算法的基本原理用一句话就可以说清楚:两个整数的最大公约数等于其中较小的数和两数的差的最大公约数,即 gcd(a, b) = gcd(b, a mod b)

为什么可以这么求呢,这里可以简单证明一下:

假设 a, b (a > b) 两个数的一个公约数是 t ,则有

因为 a > b ,设 a = k × b + r ,即 r = a mod b ,将 ab 代入展开可得:

由于 (n - k × m) × t 一定是整数,所以 ab 的公约数 t 也是 r 的约数。所以如果我们递归的求解 a mod b 也就是 a % b ,就可以得到 ab 的最大公约数 GCD 了。什么时候递归结束呢?当 a % b == 0 的时候,因为在这个过程中,如果 a mod b 无法求得正整数 r 时,则无法继续按照上述规律继续拆分。

    # pythondef gcd(a, b):        return a if b == 0 else gcd(b, a % b)
// Cint gcd(int a, int b) {        return b == 0 ? a : gcd(b, a % b);}

这里另外提一句,ab 两数的最大公倍数 LCM(a, b) = a * b / GCD(a, b) 。这里就不证明了,有兴趣的自己谷歌。

一道头条的笔试题

上个月在脉脉上看到一道头条校招的笔试题,看评论说是“地狱难度”的,我们通过这道题来延伸说一下。先来看下这题的题面:


有一台用电容组成的计算器,其中每个电容组件都有一个最大容量值(正整数)。对于单个电容,有如下操作指令:

指令1:放电操作-把该电容当前电量值清零;

指令2:充电操作-把该电容当前电量补充到最大容量值;

指令3:转移操作-从电容 A 中尽可能多的将电量转移到电容 B ,转移不会有电量损失,如果能够充满 B 的最大容量,那剩余的电量仍然会留在 A 中。

现在已知有两个电容,其最大容量分别为 ab,其初始状态都是电量值为 0,希望通过一些列的操作可以使其中某个电容(无所谓哪一个)中的电量值等于 c (c也是正整数),这一些列操作所用的最少指令条数记为 M,如果无论如何操作,都不可能完成,则定义此时 M = 0

显然对于每一组确定的 a,b,c,一定会有一个 M 与之对应。

这里需要输入的是 abc ,给出两个样例,例如 a = 3, b = 4, c = 2 ,则最少需要 4 个指令完成。


解释:设最大容量为 3 的是 A 号电容,另一个是 B 号电容,对应的操作是 (充电 A)=> (转移 A -> B) => (充电 A)=> (转移 A -> B) ,这样 A 就是目标的 2 电量。

第二个样例 a = 2, b = 3, c = 4,由于 a 和 b 都无法到目标电量 4,所以输出 0 代表无解。

这道题我们拿到以后,第一反应就是模拟三个指令,然后使用 BFS 广度优先搜索来搜出答案,只要任意情况到达目标的 c 值就停下来。但是题目中给出了数据量 0 < a, b, c < 10^9 ,这个数据量约束了我们无法使用暴力搜索来求解。

简要分析

首先从笔试的角度来分析,由于笔试时会有数据范围的测试,这道题给出的数据范围大概是这样:

0 c 10^0 c 10^0 c 10^

所以如果没有任何的思路和数论基础,我建议使用 BFS 直接写一版暴力,最少可以通过 > 50% 的数据,从而拿到一定的分数。(其实这就是 OI 得分赛制,没有思路先暴力抢分)。

下面我们来分情况讨论这个问题:

情况一

样例已经给出了一种边界情况,即当 c > max(a, b) ,这种情况是无法使得 A 和 B 的电量达到 c 的。直接输出 0。

情况二

还有一种我们可以直接想到的情况,当 a = c 或者 b = c 的时候,只进行一次充电操作就可以完成,直接输出 1。

情况三

接下来我们考虑一般情况,即需要满足以下前提条件:

我们将这个问题换一个思路转化一下假设给出的 abc 一定有解,那么我们来设置对 A 做了 x 次的充(放)电,对 B 做了 y 次的充(放)电,并且做了 k 次的操作三。如果将 A、B 当做一个大电容来看这个电容只有充放电 a 单位、充放电 b 单位这 4 种操作。那么我们就可以列出一个关系式:

由于 ab 为非负整数,又因为前提条件 c < max(a, b) ,则 xy 符号相反。

暂且,我们先不管做了几次操作三,先只考虑充放电问题,那其实就是已知 abc,我们在给定范围内求解 xy 的解就可以了。那么这个问题我们要如何求解呢?这就是扩展欧几里得算法所要解决的问题

扩展欧几里得算法(Extended Euclidean)

* 的章节略有难度。如果是从解决问题的工程角度出发,可以跳过证明直接记结论。

在推导上述问题的求解算法之前,我们需要先了解以下几个概念知识。

丢番图方程(Diophantine Equation)

丢番图方程指的是:未知数个数多于方程个数,且未知数只能是整数的整数系数方程或方程组。例如以下式中,a、b、c 都为整数:

关于代数学鼻祖丢番图(Diophantus)除了有《算数》这本开山巨作之外,还有一个好玩的数学题目墓志铭,有兴趣可以自己了解。

裴蜀定理(Bézout's identity)

在数论中,裴蜀定理是一个关于最大公约数的定理。这个定理说明了对于任意整数 a、b 和他们的最大公约数 d,关于未知数 xy 的线性丢番图方程:

有解,当且仅当 md 的倍数时。这个等式也被称为裴蜀等式。

裴蜀等式有解时必然有无穷多个整数解,每组解 xy 都称之为裴蜀数,可用辗转相除法求得。

辗转相除法实现扩展欧几里得算法

既然说可以用辗转相除法来解决这个问题,那么我们先来说明一下如何通过辗转相除法来求二元一次线性丢番图方程。

辗转相除法过程

23x + 17y = 1 为例,我们来求 GCD(23, 17)

改写成余数形式

将等式右边的第一项移项:

反向带入原式

带下划线的 65 会使用 (1)(2) 两个式子反向带入,形同换元:

所以反解得,x = 3, y = -4 是上述二元一次线性丢番图方程的一组解。

* 扩展欧几里得算法证明

来观察一下辗转相除法的最后两个式子,终止条件是:

当且仅当第二个式子为 0 的时候停止这个递归运算。如何延伸到一般情况呢?我们将待求变量设为字母来尝试一下。假设此时,我们要求 anbn 为系数的二元一次线性丢番图方程的系数,即待求方程:

根据上述的改写余数形式,我们可以列出式一(| 是整除的意思):

假设未到达最终的终止条件,则有:

第二个式子中我们可以发现:

同理,第 n 个式子中有:

根据辗转相除的规则,我们知道第 0 项中 b = 0a = 1 ,而我们要求的是第 n 项中的 ab,所以可以通过 ab 的递推公式逐一推导而来。

如此我们证明了 anbn 的递推关系,下面我们来证明 xn 的递推关系。

由上文证得了:

我们将其带入到第一个式子中:

所以可以求得:

由于辗转相除的推论我们可得:

所以:

即:

代码实现扩展欧几里得算法

为了实现上述的反向带入原式的过程,我们通过递归递归到最深的一层,将每一层的解带入即可完成最终的求解:

# pythondef ex_gcd(a, b):        if b == 0:            return 1, 0, a        else:            x, y, r = ex_gcd(b, a % b)             x, y = y, (x - (a // b) * y)            return x, y, r
// c++int ex_gcd(int a, int b, int &x, int &y) {    if(b == 0) {         x = 1;        y = 0;        return a;    }    int r = ex_gcd(b, a % b, x, y);    int t = y;    y = x - (a / b) * y;    x = t;    return r;}

但是我们注意到,由于裴蜀定理,我们求解的丢番图方程中,等号右边的常数必须是 k * gcd(a, b)。所以我们的求解其实是:

所以通过扩展 GCD 算法求得的 x0y0 这组解,并不是我们要求的最终解。同样的,我们对其扩大 k 倍就是我们想要对结果:

小结

有了这些知识,你对那道“地狱难度”的头条面试题有没有更多的想法呢?这里有一道 [LeetCode-365] 水壶问题 你可以尝试一下,做完之后想必会对扩展 GCD 算法有更深的理解。

至于头条面试题,我将在下一篇文继续讲述并代码实现此题的解法。

如何利用扩展欧几里得算法求解不定方程_客户端不用的算法系列:从头条笔试题认识扩展欧几里得算法...相关推荐

  1. 如何利用扩展欧几里得算法求解不定方程_欧几里德算法、拓展欧几里德、中国剩余定理...

    01.欧几里德算法(Euclidean algorithm)(辗转相除法) 欧几里德算法又称辗转相除法,主要是用于计算两个整数a,b的最大公约数. 简单点说一下算法原理:两个整数的最大公约数等于其中小 ...

  2. 隔板法求解不定方程x1+x2+x3=5解的个数

    隔板法求解不定方程的解的个数 文章目录 隔板法求解不定方程的解的个数 1.求正整数解的个数--普通隔板法 2.求非负整数解的个数--添加元素隔板法 1.求正整数解的个数--普通隔板法 将不定方程想象成 ...

  3. 网易笔试题之DFS回溯法求解黑白棋(翻翻棋)

    黑白棋,又叫翻转棋(Reversi).苹果棋或奥赛罗棋(Othello). 游戏规则: 棋盘共有8行8列共64格.开局时,棋盘正中央的4格先置放黑白相隔的4枚棋子(亦有求变化相邻放置).通常黑子先行. ...

  4. python实习生面试题_【实习】暑期实习之python笔试题(一)

    近期忙于找一个暑期实习的公司,无奈个人水平实在太水,合适的公司也不是很多,笔试题目也积累了一些,整理一下好了. 公司 A 题目一:编写一个脚本main.py,使用方式如下: main.py -u ht ...

  5. java 笔试题一套_软世通分享一套Java笔试题

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 7.以下程序段执行后的K值为( ). int x=20; y=30; k=(x>y)?y:x A. 20 B. 30 C.10 D.50 8.要想定 ...

  6. java笔试题_一个Java程序员在百度的笔试题整理

    以下是程序员去百度的笔试题目整理 欢迎大家讨论,文末有福利. A 卷 Java中 ++ 操作符是线程安全的吗? a=a+b 与 a+=b的区别? 写出Java的单例模式 为什么在重写equals方法的 ...

  7. java工程师考试题目_成功拿到Offer,Java工程师笔试题及答案!

    1.是否可以从一个static方法内部发出对非static方法的调用? 不可以.因为非static方法是要与对象关联在一起的,必须创建一个对象后,才可以在该对象上进行方法调用,而static方法调用时 ...

  8. 字节跳动python面试题_字节跳动2019两道春招笔试题python解法

    (一)万万没想到之聪明的编辑 我叫王大锤,是一家出版社的编辑.我负责校对投稿来的英文稿件,这份工作非常烦人,因为每天都要去修正无数的拼写错误.但是,优秀的人总能在平凡的工作中发现真理.我发现一个发现拼 ...

  9. 中级软件测试笔试题100精讲_汇集上千位软件测试精英面试笔试题,最全面的题型都在这!...

    知己知彼,才能百战不殆 测试员想要在竞争激烈的职场中拥有一席之地,就需要提前做好准备. 前人栽树,后人乘凉 一次面试就能入职自己心仪的公司,就需要通过"前人"积累的面试题来了解面试 ...

最新文章

  1. 《CUDA C编程权威指南》——1.5节总结
  2. 这场编程语言的发布会,不参加可太亏了!
  3. 数据库设计之从0到1 教你如何设计E-R图
  4. const参数,const返回值与const函数
  5. 行为设计模式 - 解释器设计模式
  6. android按钮周围阴影,Android 上的按钮填充和阴影
  7. mac使用codelite运行程序没有输出
  8. 诺基亚E5删除自己安装的应用程序
  9. azure云数据库_配置Azure SQL数据库防火墙
  10. OpenGL基础29:深度测试
  11. Java自然语言处理NLP工具包
  12. 虚拟机 网卡模式配置
  13. python2中的print语句可以不用小括号。_Python基础语法 | 代码规范amp;判断语句amp;循环语句...
  14. 台币转换计算机,Soulver 内建自动计算机功能的备忘录工具 货币换算、複杂数学式也支援...
  15. feign不能正常传递参数MultipartFile(文件)时的解决手段
  16. 抖音中用小程序自动制作人物关系图
  17. 计算机内存外存的区别
  18. python爬取游戏数据,Python 爬虫之好游快爆游戏排行信息爬取
  19. 大数据角度给大家解释一下为什么大数据AI分析足彩是扯淡
  20. wps文件上的logo怎么去掉_WPS卸载后Office图标显示出现问题怎么办?(解决方法)...

热门文章

  1. python matplotlib.pyplot中的.plot()和.scatter()以及.subplot()和.add_axes()区别
  2. Python 科学计算库 Numpy (二) —— 索引及切片
  3. 5G 非独立组网链路预算公式(笔记)
  4. MySQL5.6解压版详细安装教程(附安装配置、MySQL数据库设置root管理员密码,MySQL字符集设置问题及解决办法)
  5. Ubuntu16.04下创建工作空间并添加自己的功能包(python代码)
  6. Longest X 贪心,滑动窗口,前缀和(400)
  7. Minimum Extraction 思维
  8. EXC中时间控件的使用
  9. 在EXT中前后台传数据的方式
  10. 电脑wifi不见了_大家好,我是来给你家 WiFi 提速的