点击上方蓝字设为星标

东哥带你搞定算法~

今天来聊一道与数学运算有关的算法题目,LeetCode 372 题 Super Pow,让你进行巨大的幂运算,然后求余数。

int superPow(int a, vector<int>& b);

要求你的算法返回幂运算a^b的计算结果与 1337 取模(mod,也就是余数)后的结果。就是你先得计算幂a^b,但是这个b会非常大,所以b是用数组的形式表示的。

这个算法其实就是广泛应用于离散数学的模幂算法,至于为什么要对 1337 求模我们不管,单就这道题可以有三个难点:

一是如何处理用数组表示的指数,现在b是一个数组,也就是说b可以非常大,没办法直接转成整型,否则可能溢出。你怎么把这个数组作为指数,进行运算呢?

二是如何得到求模之后的结果?按道理,起码应该先把幂运算结果算出来,然后做% 1337这个运算。但问题是,指数运算你懂得,真实结果肯定会大得吓人,也就是说,算出来真实结果也没办法表示,早都溢出报错了。

三是如何高效进行幂运算,进行幂运算也是有算法技巧的,如果你不了解这个算法,后文会讲解。

那么对于这几个问题,我们分开思考,逐个击破。

如何处理数组指数

首先明确问题:现在b是一个数组,不能表示成整型,而且数组的特点是随机访问,删除最后一个元素比较高效。

不考虑求模的要求,以b = [1,5,6,4]来举例,结合指数运算的法则,我们可以发现这样的一个规律:

看到这,我们的老读者肯定已经敏感地意识到了,这就是递归的标志呀!因为问题的规模缩小了:

    superPow(a, [1,5,6,4])=>  superPow(a, [1,5,6])

那么,发现了这个规律,我们可以先简单翻译出代码框架:

// 计算 a 的 k 次方的结果// 后文我们会手动实现int mypow(int a, int k);

int superPow(int a, vector<int>& b) {    // 递归的 base case    if (b.empty()) return 1;    // 取出最后一个数    int last = b.back();    b.pop_back();    // 将原问题化简,缩小规模递归求解    int part1 = mypow(a, last);    int part2 = mypow(superPow(a, b), 10);    // 合并出结果    return part1 * part2;}

到这里,应该都不难理解吧!我们已经解决了b是一个数组的问题,现在来看看如何处理 mod,避免结果太大而导致的整型溢出。

如何处理 mod 运算

首先明确问题:由于计算机的编码方式,形如(a * b) % base这样的运算,乘法的结果可能导致溢出,我们希望找到一种技巧,能够化简这种表达式,避免溢出同时得到结果。

比如在二分查找中,我们求中点索引时用(l+r)/2转化成l+(r-l)/2,避免溢出的同时得到正确的结果。

那么,说一个关于模运算的技巧吧,毕竟模运算在算法中比较常见:

(a*b)%k = (a%k)(b%k)%k

证明很简单,假设:

a=Ak+B;b=Ck+D

其中 A,B,C,D 是任意常数,那么:

ab = ACk^2+ADk+BCk+BD

ab%k = BD%k

又因为:

a%k = B;b%k = D

所以:

(a%k)(b%k)%k = BD%k

综上,就可以得到我们化简求模的等式了。

换句话说,对乘法的结果求模,等价于先对每个因子都求模,然后对因子相乘的结果再求模

那么扩展到这道题,求一个数的幂不就是对这个数连乘么?所以说只要简单扩展刚才的思路,即可给幂运算求模:

int base = 1337;// 计算 a 的 k 次方然后与 base 求模的结果int mypow(int a, int k) {    // 对因子求模    a %= base;    int res = 1;    for (int _ = 0; _         // 这里有乘法,是潜在的溢出点        res *= a;        // 对乘法结果求模        res %= base;    }    return res;}

int superPow(int a, vector<int>& b) {    if (b.empty()) return 1;    int last = b.back();    b.pop_back();

    int part1 = mypow(a, last);    int part2 = mypow(superPow(a, b), 10);    // 每次乘法都要求模    return (part1 * part2) % base;}

你看,先对因子a求模,然后每次都对乘法结果res求模,这样可以保证res *= a这句代码执行时两个因子都是小于base的,也就一定不会造成溢出,同时结果也是正确的。

至此,这个问题就已经完全解决了,已经可以通过 LeetCode 的判题系统了。

但是有的读者可能会问,这个求幂的算法就这么简单吗,直接一个 for 循环累乘就行了?复杂度会不会比较高,有没有更高效的算法呢?

有更高效的算法的,但是单就这道题来说,已经足够了。

因为你想想,调用mypow函数传入的k最多有多大?k不过是b数组中的一个数,也就是在 0 到 9 之间,所以可以说这里每次调用mypow的时间复杂度就是 O(1)。整个算法的时间复杂度是 O(N),N 为b的长度。

但是既然说到幂运算了,不妨顺带说一下如何高效计算幂运算吧。

如何高效求幂

快速求幂的算法不止一个,就说一个我们应该掌握的基本思路吧。利用幂运算的性质,我们可以写出这样一个递归式:

这个思想肯定比直接用 for 循环求幂要高效,因为有机会直接把问题规模(b的大小)直接减小一半,该算法的复杂度肯定是 log 级了。

那么就可以修改之前的mypow函数,翻译这个递归公式,再加上求模的运算:

int base = 1337;

int mypow(int a, int k) {    if (k == 0) return 1;    a %= base;

    if (k % 2 == 1) {        // k 是奇数        return (a * mypow(a, k - 1)) % base;    } else {        // k 是偶数        int sub = mypow(a, k / 2);        return (sub * sub) % base;    }}

这个递归解法很好理解对吧,如果改写成迭代写法,那就是大名鼎鼎的快速幂算法。至于如何改成迭代,很巧妙,这里推荐一位大佬的文章 让技术一瓜共食:快速幂算法。

虽然对于题目,这个优化没有啥特别明显的效率提升,但是这个求幂算法已经升级了,以后如果别人让你写幂算法,起码要写出这个算法。

至此,Super Pow 就算完全解决了,包括了递归思想以及处理模运算、幂运算的技巧,可以说这个题目还是挺有意思的,你有什么有趣的题目,可以留言分享一下。

历史文章:

回溯算法团灭排列/组合/子集问题

加密算法的前世今生

这个问题不简单:寻找缺失元素

幂运算 数组_Super Pow:如何高效进行模幂运算相关推荐

  1. c语言的 pow函数是快速幂吗,leetcode 50. Pow(x, n)(快速幂)

    就是一个二分法快速幂. 但是需要注意的问题是这里是实数,而且n可能为负. int的范围是-2,147,483,648 至 2,147,483,647.如果为-2,147,483,648那么直接n=-n ...

  2. C++modular exponentiation模幂运算的实现算法(附完整源码)

    C++modular exponentiation模幂运算的实现算法 C++modular exponentiation模幂运算的实现算法完整源码(定义,实现,main函数测试) C++modular ...

  3. 蒙哥马利算法(Montgomery Algorithm)|蒙哥马利约简、模乘、模幂

    Montgomery Algorithm(蒙哥马利算法) 蒙哥马利算法分为3种,蒙哥马利模乘,蒙哥马利约简,蒙哥马利模幂 1.从蒙哥马利模乘说起 模乘是为了计算ab(modN)ab\pmod{N}ab ...

  4. 基于Montgomery算法的高速、可配置 RSA密码IP核硬件设计系列(五)——模幂模块(抵抗侧信道攻击)模块的设计实现方案

    基于Montgomery算法的高速.可配置RSA密码IP核硬件设计系列(五) 2.2 模幂模块设计(抵抗测信道攻击模块) 2.2.1 模幂模块及内部模块的功能 2.2.3 模幂各模块的实现方案 2.2 ...

  5. 如何高效进行模乘、模幂运算?——蒙哥马利算法(Montgomery Algorithm)从入门到精通

    蒙哥马利算法(Montgomery Algorithm)从入门到精通 ​ 加密算法中,模运算(包括模乘.模幂运算)是难以避免的,如何高效地进行模运算,是提高算法效率的一个关键. 直观的想法 ​ 在数学 ...

  6. 保研机试——2数学问题(简单数学、最大公约/最小公倍、分数运算、素数、质因子分解、快速幂、高精度问题、常见数学公式总结、规律神器OEIS)

    1 简单数学 2 最大公约/最小公倍 3 分数运算 4 素数 5 快速幂 5 高精度问题 6 常见数学公式总结 7 规律神器OEIS 1 简单数学 (1)同余模定理:所谓的同余,顾名思义,就是许多的数 ...

  7. python 数组 运算_python数据分析(二) python numpy--数组和矢量运算--数组对象

    Numpy numpy是数值计算最重要的基础包,几乎所有的科学运算的模块底层所用的都是numpy数组. Numpy本身没有提供多么高级的数据分析功能,他所提供的功能主要是: 1.具有矢量算术运算(用数 ...

  8. 【C 语言】指针 与 数组 ( 指针 | 数组 | 指针运算 | 数组访问方式 | 字符串 | 指针数组 | 数组指针 | 多维数组 | 多维指针 | 数组参数 | 函数指针 | 复杂指针解读)

    相关文章链接 : 1.[嵌入式开发]C语言 指针数组 多维数组 2.[嵌入式开发]C语言 命令行参数 函数指针 gdb调试 3.[嵌入式开发]C语言 结构体相关 的 函数 指针 数组 4.[嵌入式开发 ...

  9. Modular_exponentiation模幂运算

    https://en.wikipedia.org/wiki/Modular_exponentiation 蒙哥马利(Montgomery)幂模运算是快速计算a^b%k的一种算法,是RSA加密算法的核心 ...

最新文章

  1. 还在忍受限速网盘?来搭建一套自己的私有网盘!
  2. python远程桌面控制_手把手教你如何用Pycharm2020.1.1配置远程连接的详细步骤
  3. Tomcat实现session的代码逻辑分析
  4. 新加坡樟宜机场将进入“刷脸”时代 自助通关是亮点
  5. jsp 构建单页应用_如何使用服务器端Blazor构建单页应用程序
  6. 网页实现凭证金额分割线_一位整理过5000个网页书签的大神分享:实用的书签管理方案...
  7. BugkuCTF-MISC题简单套娃
  8. Leetcode 172 Factorial Trailing Zeroes
  9. 抱歉!我不太会修电脑 | 2018年中国程序员研究报告
  10. CS229 6.18 CNN 的反向传导算法
  11. 软件开发高手须掌握的4大SQL精髓语句(综合篇)
  12. 历史上的今天:首条海底光缆开通;VeriSign 收购 Network Solutions;计算机图形学先驱诞生...
  13. java 一元线性回归_Java中的多元线性回归
  14. 技能梳理8@esp8266+lua+onenet+血氧检测MAX30102
  15. 软工网络15团队作业8——Beta阶段冲刺合集
  16. CISSP-OSG-每章小结梳理
  17. 最新TIOBE编程语言排行:C语言第一,Python反超Java,挤进第二
  18. Intellij Idea 所有快捷键列表,工欲善其事必先利其器。
  19. oracle 企业管理器网页打不开 解决https://localhost:1158/em问题
  20. Python3之基础语法

热门文章

  1. php处理不确定笛卡尔积,PHP笛卡尔积实现算法示例
  2. 在Office 2007 Word文档中插入两种页码方法
  3. python查看安装包的版本scipy,numpy,matplotlib等
  4. LINUX CentOS6.8 wdcp面板安装
  5. go的异常处理,defer,panic,recover
  6. Sandcastle帮助文档生成器使用介绍
  7. OAF中下载附件之后页面失效,报过时的数据异常,浏览器后退异常
  8. DOS命令输出的重定向
  9. 最新mysql5.7.12 win64 安装及配置
  10. ReduceTask工作机制