数论


一、引言

        对于 LeetCode 上数量不少的数学题,我们尽量将其按照类型划分讲解。然而很多数学题的解法并不通用,我们也很难在几道题里把所有的套路讲清楚,因此我们只选择了几道经典或是典型的题目,供大家参考。


二、经典问题

1. 公倍数与公因数

        利用辗转相除法欧几里得算法),我们可以很方便地求得两个数的最大公因数(greatest common divisor,gcd);将两个数相乘再除以最大公因数即可得到最小公倍数(least common multiple, lcm)。
int gcd(int a, int b){return b == 0? a: gcd(b, a%b);
}
int lcm(int a, int b){return a / gcd(a, b) * b;
}

进一步地,我们也可以通过扩展欧几里得算法(extended gcd)在求得 a 和 b 最大公因数的同时,也得到它们的系数 x 和 y,从而使 ax + by = gcd(a, b)。

int xGCD(int a,int b, int &x, int &y){if(!b){x = 1, y = 0;return a;}int x1, y1,;int gcd = xGCD(b, a%b, x1, y1);x = y1, y = x1 - (a / b) * y1;return gcd;
}

2. 质数

        质数又称素数,指的是指在大于 1 的自然数中,除了 1 和它本身以外不再有其他因数的自然 数。值得注意的是,每一个数都可以分解成质数的乘积。
        埃拉托斯特尼筛法(Sieve of Eratosthenes,简称埃氏筛法)是非常常用的,判断一个整数是否是质数的方法。并且它可以在判断一个整数 n 时,同时判断所小于 n 的整数,因此非常适合这道题。其原理也十分易懂:从 1 到 n 遍历,假设当前遍历到 m,则把所有小于 n 的、且是 m 的倍数的整数标为和数;遍历完成后,没有被标为和数的数字即为质数。

204. 计数质数

204. Count Primes

给定整数 n ,返回 所有小于非负整数 n 的质数的数量 。

class Solution {
public:int countPrimes(int n) {if(n <= 2)  return 0;vector<bool> prime(n, true);int count = n - 2;for(int i=2; i<n; ++i){if(prime[i]){for(int j=2*i; j<n; j+=i){if(prime[j]){prime[j] = false;--count;}}}}return count;}
};
        利用质数的一些性质,我们可以进一步优化该算法。
class Solution {
public:int countPrimes(int n) {if(n <= 2)  return 0;vector<bool> prime(n, true);int count = n / 2; // 所有的偶数一定不是质数int i = 3, sqrtn = sqrt(n);while(i <= sqrtn){ // 最小质因子一定小于等于开方数for(int j=i*i; j<n; j+=2*i){ // 避免偶数和重复遍历if(prime[j]){prime[j] = false;--count;}}do{i += 2;}while(i <= sqrtn && !prime[i]); // 避免偶数和重复遍历}return count;}
};

3. 数字处理

504. 七进制数

504. Base 7

给定一个整数 num,将其转化为 7 进制,并以字符串形式输出。

        进制转换类型的题,通常是利用除法和取模(mod)来进行计算,同时也要注意一些细节,如 负数和零。如果输出是数字类型而非字符串,则也需要考虑是否会超出整数上下界。
class Solution {
public:string convertToBase7(int num) {if(num == 0)    return "0";bool is_negative = num < 0;if(is_negative) num = -num;string ans;while(num){int a = num / 7, b = num % 7;ans = to_string(b) + ans;num = a;}return is_negative? "-" + ans: ans;}
};

172. 阶乘后的零

172. Factorial Trailing Zeroes

给定一个整数 n ,返回 n! 结果中尾随零的数量。

提示 n! = n * (n - 1) * (n - 2) * ... * 3 * 2 * 1

        每个尾部的 0 由 2 × 5 = 10 而来,因此我们可以把阶乘的每一个元素拆成质数相乘,统计有多少个 2 和 5。明显的,质因子 2 的数量远多于质因子 5 的数量,因此我们可以只统计阶乘结果里有多少个质因子 5。
class Solution {
public:int trailingZeroes(int n) {if(n == 0)  return 0;return n / 5 + trailingZeroes(n / 5); }
};

415. 字符串相加

415. Add Strings

给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和并同样以字符串形式返回。

你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。

class Solution {
public:string addStrings(string num1, string num2) {string output("");reverse(num1.begin(), num1.end());reverse(num2.begin(), num2.end());int onelen = num1.length(), twolen = num2.length();if(onelen <= twolen){swap(num1, num2);swap(onelen, twolen);}int addbit = 0;for(int i=0; i<twolen; ++i){int cur_sum = (num1[i] - '0') + (num2[i] - '0') + addbit;output += to_string((cur_sum) % 10);addbit = cur_sum < 10? 0: 1;}for(int i=twolen; i<onelen; ++i){int cur_sum = (num1[i] - '0') + addbit;output += to_string((cur_sum) % 10);addbit = cur_sum < 10? 0: 1;}if(addbit){output += "1";}reverse(output.begin(), output.end());return output;}
};

326. 3 的幂

326. Power of Three

给定一个整数,写一个函数来判断它是否是 3 的幂次方。如果是,返回 true ;否则,返回 false 。

整数 n 是 3 的幂次方需满足:存在整数 x 使得 n == 3^x

        有两种方法,一种是利用对数。设 ,如果 n 是 3 的整数次方,那么 m 一定是整数。
class Solution {
public:bool isPowerOfThree(int n) {return fmod(log10(n) / log10(3), 1) == 0;}
};
        另一种方法是,因为在 int 范围内 3 的最大次方是 3^19 = 1162261467,如果 n 是 3 的整数次方,那么 1162261467 除以 n 的余数一定是零;反之亦然。
class Solution {
public:bool isPowerOfThree(int n) {return n > 0 && 1162261467 % n == 0;}
};

4. 随即与取样

384. 打乱数组

384. Shuffle an Array

给你一个整数数组 nums ,设计算法来打乱一个没有重复元素的数组。打乱后,数组的所有排列应该是 等可能 的。

实现 Solution class:

Solution(int[] nums) 使用整数数组 nums 初始化对象
        int[] reset() 重设数组到它的初始状态并返回
        int[] shuffle() 返回数组随机打乱后的结果

        我们采用经典的 Fisher-Yates 洗牌算法,原理是通过随机交换位置来实现随机打乱,有正向和反向两种写法,且实现非常方便。注意这里“reset”函数以及类的构造函数的实现细节。
class Solution {
public:vector<int> origin;Solution(vector<int>& nums): origin(std::move(nums)) {}vector<int> reset() {return origin;}vector<int> shuffle() {if(origin.empty())  return {};vector<int> shuffled(origin);int n = origin.size();//反向洗牌for(int i=n-1; i>=0; --i){swap(shuffled[i], shuffled[rand() % (i + 1)]);}//正向洗牌// for(int i=0; i<n; ++i){//     int pos = rand() % (n - i);//     swap(shuffled[i], shuffled[i + pos]);// }return shuffled;}
};/*** Your Solution object will be instantiated and called as such:* Solution* obj = new Solution(nums);* vector<int> param_1 = obj->reset();* vector<int> param_2 = obj->shuffle();*/

528. 按权重随机选择

528. Random Pick with Weight

给你一个 下标从 0 开始 的正整数数组 w ,其中 w[i] 代表第 i 个下标的权重。

请你实现一个函数 pickIndex ,它可以 随机地 从范围 [0, w.length - 1] 内(含 0 和 w.length - 1)选出并返回一个下标。选取下标 i 的 概率 为 w[i] / sum(w) 。

例如,对于 w = [1, 3],挑选下标 0 的概率为 1 / (1 + 3) = 0.25 (即,25%),而选取下标 1 的概率为 3 / (1 + 3) = 0.75(即,75%)。

        我们可以先使用 partial_sum 求前缀和(即到每个位置为止之前所有数字的和),这个结果对于正整数数组是单调递增的。每当需要采样时,我们可以先随机产生一个数字,然后使用二分法查找其在前缀和中的位置,以模拟加权采样的过程。这里的二分法可以用 lower_bound 实现。
        以样例为例,权重数组[1,3]的前缀和为[1,4]。如果我们随机生成的数字为1,那么 lower_bound 返回的位置为 0;如果我们随机生成的数字是 2、3、4,那么 lower_bound 返回的位置为 1。
        关于前缀和的更多技巧,我们将在接下来的章节中继续深入讲解。
class Solution {
public:vector<int> sums;Solution(vector<int>& w): sums(std::move(w)) {partial_sum(sums.begin(), sums.end(), sums.begin());}int pickIndex() {int pos = (rand() % sums.back()) + 1;return lower_bound(sums.begin(), sums.end(), pos) - sums.begin();}
};/*** Your Solution object will be instantiated and called as such:* Solution* obj = new Solution(w);* int param_1 = obj->pickIndex();*/

382. 链表随机节点

382. Linked List Random Node

给你一个单链表,随机选择链表的一个节点,并返回相应的节点值。每个节点 被选中的概率一样 。

实现 Solution 类:

Solution(ListNode head) 使用整数数组初始化对象。
        int getRandom() 从链表中随机选择一个节点并返回该节点的值。链表中所有节点被选中的概率相等。

        不同于数组,在未遍历完链表前,我们无法知道链表的总长度。这里我们就可以使用水库采样:遍历一次链表,在遍历到第 m 个节点时,有   的概率选择这个节点覆盖掉之前的节点选择。
        我们提供一个简单的,对于水库算法随机性的证明。对于长度为 n 的链表的第 m 个节点,最 后被采样的充要条件是它被选择,且之后的节点都没有被选择。这种情况发生的概率为   。因此每个点都有均等的概率被选择。
/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode() : val(0), next(nullptr) {}*     ListNode(int x) : val(x), next(nullptr) {}*     ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/
class Solution {
public:ListNode* head;Solution(ListNode* n): head(n) {}int getRandom() {int ans = head->val;ListNode* node = head->next;int i = 2;while(node){if((rand() % i) == 0){ans = node->val;}++i;node = node->next;}return ans;}
};/*** Your Solution object will be instantiated and called as such:* Solution* obj = new Solution(head);* int param_1 = obj->getRandom();*/

三、巩固练习

168. Excel表列名称

168. Excel Sheet Column Title

67. 二进制求和

67. Add Binary

238. 除自身以外数组的乘积

238. Product of Array Except Self

462. 最小操作次数使数组元素相等 II

462. Minimum Moves to Equal Array Elements II

169. 多数元素

169. Majority Element

470. 用 Rand7() 实现 Rand10()

470. Implement Rand10() Using Rand7()

202. 快乐数

202. Happy Number


欢迎大家共同学习和纠正指教

LeetCode力扣刷题——巧解数学问题相关推荐

  1. IDEA LeetCode力扣刷题插件 中文类名 模板

    在用idea力扣插件刷题时,用中文作为类名会出现空格或者其他符号,总所周知,变量名和类名是不能出现空格和其他符号的,在我查阅了官方文档后,发现模板的工具类是继承了Stringutils的,所以在代码模 ...

  2. leetcode(力扣)刷题题解网站

    中文题解直接看力扣的题解 如果想要看国外大神的相关题解,只需要将中文题解网址中的'-cn' 去掉即可 英文题解网址: https://leetcode.com/problems/binary-tree ...

  3. LeetCode力扣刷题数据库(178):分数排名

    178分数排名 表: Scores +-------------+---------+ | Column Name | Type | +-------------+---------+ | id | ...

  4. LeetCode力扣刷题——居合斩!二分查找

    二分查找 一.算法解释         二分查找也常被称为二分法或者折半查找,每次查找时通过将待查找区间分成两部分并只取 一部分继续查找,将查找的复杂度大大减少.对于一个长度为 O ( n ) 的数组 ...

  5. LeetCode力扣刷题——千奇百怪的排序算法

    排序算法 一.常见的排序算法         以下是一些最基本的排序算法.虽然在 C++ 里可以通过 std::sort() 快速排序,而且刷题时很少需要自己手写排序算法,但是熟习各种排序算法可以加深 ...

  6. leetcode力扣刷题系列python——2、两数相加

    题目: 给出两个 非空 的链表用来表示两个非负的整数.其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字. 如果,我们将这两个数相加起来,则会返回一个新的链表来表示 ...

  7. Leetcode力扣刷题

    704. 二分查找 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1. clas ...

  8. LeetCode力扣刷题——简单易懂的贪心算法

    贪心 一.算法解释 采用贪心的策略,保证每次操作都是局部最优的,从而使最后得到的结果是全局最优的. 贪心算法问题需要满足的条件: (1)最优子结构:规模较大的问题的解由规模较小的子问题的解组成,规模较 ...

  9. 教你创建电脑、手机同步的markdown云笔记--力扣刷题力荐!

    开篇先致歉 其他不谈,开篇必须先给各位读者道个歉,年后工作上比较忙,加上最近闲暇的时间都用来在力扣上刷算法题了,导致公众号断更有些严重啊.再加上年后将健身减重提上了日程,时间上就更显的捉襟见肘了. 不 ...

最新文章

  1. c++编译优化,arm优化
  2. URL模块之parse方法
  3. 解决pom文件第一行报错(unknown)-亲测有效
  4. 希望查询windows下安装cygwin后ssh服务无法启动的解决办法
  5. “假冒上市”公司再甩锅:我被服务商给骗了
  6. 无废话C#设计模式系列文章
  7. 9.程序员的自我修养---Windows下的动态链接
  8. Mysql5.7+ 出现Field * doesn‘t have a default value解决办法
  9. linux+极点五笔输入法,CentOS系统安装极点五笔输入法
  10. 微信公众号开发详细过程(亲手总结)
  11. 【我的OpenGL学习进阶之旅】学习OpenGL ES 3.0 的实战 Awsome Demo (下)之 图片转场和轮播特效专场
  12. 小明左右手分别拿两张纸牌:黑桃10和红心8,现在交换手中的牌。编写并输出互换后的结果
  13. 自媒体平台搜狗号登陆 搜狗挑战百度、头条有胜算吗?
  14. 数据分析|爬取14455个基金,千万别被人当成韭菜给割了
  15. Ubuntu Linux 15.04安装 nginx + passenger
  16. LeaRun.net快速开发动态表单
  17. ArrayList 和 Vector 的区别
  18. 《番茄工作法图解》书摘
  19. 如何解决Namespace declaration statement has to be the very first statement or after any declare call
  20. 大学生生涯人物访谈(程序员篇)

热门文章

  1. MySQL锁 —— 记录锁、间隙锁、临键锁、自增锁
  2. HGDB 数据库用户密码参数查询设置
  3. 全国大学生“高教杯“成图大赛:轴类零件的快速建模
  4. DDR3的一些基本知识
  5. 忽略wx公众号网页授权域名验证
  6. 微信小程序——map用法
  7. Ubuntu卸载、安装
  8. MySQL GROUP_CONCAT()函数的排序方法
  9. 域名邮箱的创建,以及如何替换原有QQ邮箱,创建自己的邮箱
  10. HDU CCPC网络选拔赛 6441 Find Integer(数学)