算法通关村-----数论问题解析
最大公约数和最小公倍数
概念描述
最大公约数(GCD)是指两个或多个整数共有约数中的最大值。
最小公倍数(LCM)是指两个或多个整数共有的倍数中的最小值
方法介绍
碾转相除法
一种用于计算两个整数的最大公约数(GCD)的方法。它基于以下原理:两个数的最大公约数等于其中较小数除以它们的余数的最大公约数。
该算法的步骤如下:
- 将两个整数记为a和b,其中a是较大的数,b是较小的数。
- 用a除以b,得到一个商q和余数r。
- 如果r等于0,则b就是最大公约数,算法结束。
- 如果r不等于0,则将a更新为b,b更新为r,然后返回第二步继续执行。
- 重复执行步骤2-4,直到余数为0为止。
两个数的乘积除以两个数的最大公约数即为两个数的最小公倍数
代码实现
求解最大公约数
public int gcd(int a, int b) {
int k = 0;
while (k!=0){
k = a%b;
a = b;
b = k;
};
return a;
}
求解最小公倍数
public int mcl(int a, int b) {
int gcd = gcd(a, b);
return a * b / gcd;
}
素数与合数
概念介绍
素数又称为质数,素数首先要满足大于等于2,并且除了1和它本身之外,不能被任何其他自然数整除。其他数都是合数。比较特殊的是1即非素素数,也非合数。2是唯一的同时为偶数和素数的数字。
方法介绍
要判断一个数是否为质数,可以使用以下方法:
- 特殊情况处理:首先排除小于2的数,因为质数定义为大于1的自然数。
- 试除法:从2开始,逐个将待判断的数除以从2到其平方根范围内的所有整数(包括平方根),如果能够整除,则该数不是质数。如果在整个范围内都没有找到能整除的数,则该数是质数。
代码实现
public boolean isPrime(int num){
int max = (int)Math.sqrt(num);
for (int i = 2; i<=max;i++){
if(num%i==0){
return false;
}
}
return true;
}
质数计数
问题描述
给定整数 n ,返回所有小于非负整数 n 的质数的数量 。详见leetcode204
问题分析
最直接的方式就是从2开始遍历,每次利用上面的质数判断代码对每一个数字进行判断,如果是质数,计数器➕1,直至计数器为n,返回当前质数。这样做时间复杂度过高。可以通过空间换时间的方式来操作,设置一个长度为n的数组,初始时,设置数组全为1,之后,从下标2开始遍历,如果数组当前值为1,质数计数器➕1,并将当前数字的倍数以及与倍数相关的非质数下标设置为0,最后返回质数计数器的值。这种方式也被称为埃氏筛
代码实现
直接方式
public int countPrimes(int n) {
int count = 0;
for(int i=2;i<n;i++){
if(isPrime(i)){
count++;
}
}
return count;
}
public boolean isPrime(int num){
int max = (int)Math.sqrt(num);
for (int i = 2; i<=max;i++){
if(num%i==0){
return false;
}
}
return true;
}
埃氏筛方式
public int countPrimes(int n) {
int[] isPrime = new int[n];
int count = 0;
Arrays.fill(isPrime,1);
for(int i=2;i<n;i++){
if(isPrime[i]==1){
count++;
if((long)i*i<n){
for(int j=i*i;j<n;j+=i){
isPrime[j]=0;
}
}
}
}
return count;
}
丑数判断
问题描述
丑数 就是只包含质因数 2、3 和 5 的正整数。
给你一个整数 n ,请你判断 n 是否为 丑数 。如果是,返回 true ;否则,返回 false 。详见leetcode263
问题分析
可以直接根据丑数的概念进行判断。如果不包含质因数或者包含质因数 2、3 和 5 的正整数即为丑数
代码实现
public boolean isUgly(int n) {
if(n<=0){
return false;
}
int[] factors = new int[]{2,3,5};
for(int factor:factors){
while(n%factor==0){
n = n/factor;
}
}
return n==1;
}
丑数计数
问题描述
给你一个整数 n ,请你找出并返回第 n 个 丑数 。详见leetcode264
问题分析
最直接的方式就是从1开始遍历,每次利用上面的丑数判断代码对每一个数字进行判断,如果是质数,计数器➕1,直至计数器为n,返回当前丑数。这样做时间复杂度过高。可以通过空间换时间的方式来操作,设置一个小顶堆,初始时,将最小的丑数1加入队中,循环n次,每次将堆顶元素x移除,每次将2x,3x,5x放入堆中,为了避免重复,可以使用集合过滤,循环n次之后,最小的第n个丑数出堆返回
代码实现
直接方式
public int nthUglyNumber(int n) {
int count = 0;
int i=1;
while(true){
if(isUgly(i)){
count++;
if(count==n){
return i;
}
}
i++;
}
}
public boolean isUgly(int num){
if(num<=0){
return false;
}
int[] factors = new int[]{2,3,5};
for(int factor:factors){
while(num%factor==0){
num = num/factor;
}
}
return num==1;
}
最小堆方式
public int nthUglyNumber(int n) {
int[] factors = {2, 3, 5};
Set<Long> seen = new HashSet<Long>();
PriorityQueue<Long> heap = new PriorityQueue<Long>();
seen.add(1L);
heap.offer(1L);
int ugly = 0;
for (int i = 0; i < n; i++) {
long curr = heap.poll();
ugly = (int) curr;
for (int factor : factors) {
long next = curr * factor;
if (seen.add(next)) {
heap.offer(next);
}
}
}
return ugly;
}