这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战
最近碰到一个面试题目,比较简单,基本上一看就能想出一种解法,不过真的这么简单吗? 我们先来看看这道题目:
给定整数m,n 统计二者之间可以被2和3整除的整数的数量(包括m,n)
0<m<=n<=10的12次方
这道题很简单,只要扫一眼就能得出第一种解法
解法一
private static long remainderCount1(long m,long n){
long count = 0;
for (long i = m; i <= n; i++) {
if (i % 2 == 0 && i % 3 == 0){
count++;
}
}
return count;
}
这种解法时间复杂度为O(n),空间复杂度为O(1)。
但如果经过仔细的思考,发现这种题目可以通过数学的方法来降低时间复杂度,类似于高斯算法,将“1+2+3+4+5+···+(n-1)+n”这样的计算提炼出公式,改为以首项加末项乘以项数除以2用来计算,也就是n*(n+1)/2。
那么这道题能否也转成公式呢?
我们先来看看,能被2和3整除的数有什么特点,它的特点就是能被2和3的最小公倍数整除。
那么此时,我们可以计算2和3的最小公倍数为6,那么我们现在就可以从所给数字中寻找所有6的倍数了,那么这该如何寻找呢?
很简单,直接除以6,就能得知里面有多少个数字能6整除,但这里还要注意一点,就是,如何处理数字的边界问题。为了方便我们来举例说明。当m=10,n=23时,里面只有两个数12,18那么,我们可以抛弃掉12之前的数字,将前面的指针向前推进到第一个符合要求的数字即可,定义一个起始数值为pre = m + (6 - m % 6);而后面的n,我们同样,只需要向前退到数列中最后一个符合条件的数字即可,suf = n + (6 - n % 6)。由此可得出解法二。
解法二:
private static long remainderCount2(long m, long n) {
long pre, suf;
//计算范围内第一个符合的数
if (m % 6 == 0) {
pre = m;
} else {
pre = m + (6 - m % 6);
}
//计算范围内最后一个符合的数
if (n % 6 == 0) {
suf = n;
} else {
suf = n - (n % 6);
}
//计算符合的个数
long count = (suf - pre) / 6 + 1;
return count;
}
解法二的解法时间复杂度为O(n),空间复杂度为O(1)。
那么我们能不能通过解法二,将“能被2和3整除的数”推出“能被x和y整除的数”这种更广义的范围呢?
当然可以,我们只需要先求出x和y的最小公倍数就行了。
给定整数m,n 统计二者之间可以被x和y整除的整数的数量(包括m,n)
0<m<=n<=10的12次方 直接套用模板
private static long remainderCount2(long m, long n,int x,int y) {
long pre, suf;
int lcm = leastCommonMultiple(x,y);
//计算范围内第一个符合的数
if (m % lcm == 0) {
pre = m;
} else {
pre = m + (lcm - m % lcm);
}
//计算范围内最后一个符合的数
if (n % lcm == 0) {
suf = n;
} else {
suf = n - (n % lcm);
}
//计算符合的个数
long count = (suf - pre) / lcm + 1;
return count;
}
private static int leastCommonMultiple(int x,int y) {
int max = (x > y) ? x : y;
int ans = max;
for (int i = max; i <= x * y; i++) {
if ((i % x == 0) && (i % y == 0)) {
System.out.println("最小公倍数为:" + i);
ans = i;
break;
}
}
return ans;
}