在这个系列中,博主准备分享每日在万人千题社区打卡学习的算法。博主也是小白,因此也很能理解新手在刷题时的困惑,所以关注博主,每天学习一道算法吧。同时也欢迎大家加入万人千题习活动,正所谓:一个人可以走的很快,但一群人才能走的更远。
万人千题打卡社区https://bbs.csdn.net/forums/hero?category=0https://bbs.csdn.net/forums/hero?category=0但如果你觉得算法太难,也可以打卡社区最新推出《C语言入门100例》,学习基础的套路,帮助你更扎实的掌握。
目录
一、知识复习
二、最大公因数
三、最小公倍数
四、简单题
五、困难题
我们之前学习了很多关于质数的算法思想,现在来回顾一下吧。
·素数的判定:遍历 i*i<=n 的所有数(试除法)
·素数的筛选:①双重循环的试除法 ②埃氏筛 ③欧拉筛 素数筛选法
·算数基本定理:任何一个数都可以唯一分解成质数的乘积(理论基础)
·因子数的计算:根据算数基本定理,利用素数筛选的方法,将一个数分解成多个质因子相乘。由于任何一个因子都是几个质因子相乘得到的,因此我们有:
【如n=2*2*2*3*3*5,则因数数有 (3+1)* (2+1)*(1+1)个。对于质因数2来说,可以取得范围在0~3之间,所以一共有3+1种选择,依次类推】
【以n=2*2*2*3*3为例。如果只取一个2,那么3的取法有三种,这一分支的和为2*(3^0+3*1+3*3),而2的取法可以为0~3,根据上面的式子利用乘法分配律可得(2^0+2^1+2^2+2^3)*(3^0+3*1+3*3)对上面的式子不断推演,就可以得出因子和】
根据算数基本定理,我们可以将任意两个式子分解成如下的形式:
而最大公因数则满足下面的式子:
于是我们想到了辗转相除法:
int gcd(int a, int b)
{
return !b ? a : gcd(b , a % b);
}
先说是怎么想到的:
假设两个数的最大公因数为k,则a=m*k,b=n*k,在这里mn互为质数,进行辗转相除的过程中mn最后一定会将mn其中一个消成1,否则a%b不可能为0,因为mn互为质数。那么1*k的数就是我们的最大公因数了。
再说说这段代码:
应用条件运算符,除非b=0(此时a可以整除b,说明mn其中一个消成0了),否则持续递归
上面提到,最小公因数实际为x,y的较小值。而最大公倍数则是两者的较大值。
那如何得到较大值呢,其实很简单,(x1 * x2)/ min (x1, x2) ,所以我们可以写出以下代码:
int gcd(int a, int b)
{
return !b ? a : gcd(b , a % b);
}
int lcm(int a, int b)
{
return a / gcd(a, b) * b;
}
为什么不写成 a * b / gcd(a, b) 呢?
a,b都是在整数范围内,而a*b则可能溢出,所以先除再乘避免溢出。看,微小的差别也可以带来意想不到的结果。
找出数组的最大公约数https://leetcode-cn.com/problems/find-greatest-common-divisor-of-array/https://leetcode-cn.com/problems/find-greatest-common-divisor-of-array/①题目呈现
②思路分析
题目很简单,运用上面讲的gcd函数就可以解决
③参考
int gcd(int a, int b)
{
return !b ? a : gcd(b , a % b);
}
int findGCD(int* nums, int numsSize)
{
int max = 1;
int min = 1000;
for (int i = 0;i < numsSize; i++)
{
if(nums[i] > max)
max = nums[i];
if(nums[i] < min)
min = nums[i];
}
return gcd(max, min);
}
①题目呈现
②思路分析
本题的难点在于子序列的情况有很多,并且我们设计的gcd函数只能求两个数的最大公因数,给我们一串的数字求出最大公因数,从正面突破显然十分困难。但坚持一个原则:正难则反。我们能否逆向思维对所有的情况进行枚举呢?我们只需要找到一种逻辑使得他能够遍历所有的情况即可,因此我们选择最简单最稳定也最容易表示的公因数作为我们的切入点。我们以公因数为标准进行枚举,代码如下:
③参考
#define maxn 200001//数组可以适当开大一点,这样我们就没必要考虑边界问题
bool f[maxn];//bool类型用来存储nums数组里的数字是否出现过
int gcd(int a, int b)//求最大公因数函数
{
return !b? a : gcd(b,a % b);
}
int MAX(int a, int b)//求两数较大值
{
return a > b? a : b;
}
int countDifferentSubsequenceGCDs(int* nums, int numsSize)
{
int m = 0;
int cnt = 0;
memset(f, 0, sizeof(f));//初始化为0,认为所有数字没有出现
for(int i = 0;i < numsSize; i++)//nums数组中出现的数则在f[]中记录一下
{
f[nums[i]] = 1;//这里不是f[nusm[i]+1],因为下面使用的时候也没有+1
m = MAX(m, nums[i]);
}
for(int i = 1; i <=m; i++)//枚举所有可能为公约数i
{
int tmp =0;
for(int j = i; j <= m ; j+=i)//遍历数组f[],从中找出nums数组中出现过的数
{
if(f[j])
tmp = gcd(tmp,j);
}
if(tmp == i)//若i与nums数组中的数比对后仍然是最大公因数说明确实是一个解
cnt++;
}
return cnt;
}
题后反思:
这道题目也给了我们启发:一个复杂的问题只要找到一个合适的逻辑能够遍历所有情况就可以解决问题。我们在寻找枚举条件时抓住本质有意向不到的效果。