Leetcode刷题系列(一)——巧解数学问题

1.引言

  在Leetcode上刷题时,会遇到一些用数据结构和常用算法无法解决的数学问题,巧妙地运用一些数学知识可以很快地解决这些问题。下面我们将介绍一些常用的解决数学问题的技巧,包括但不限于:最大公约数与最小公倍数、筛法和打表、同余定理、容斥原理、中位数定理等。

2.最大公约数与最小公倍数

  对于两个正整数a和b,其最最大公约数与最小公倍数的乘积等于两数之积:

最大公约数 × 最小公倍数 = a × b

2.1最大公约数

  辗转相除法,又名欧几里得算法,是求两个数的最大公约数(the Great Common Divisor,GCD)的一种方法,具体做法是:用较大数除以较小数,再用出现的余数(第一余数)去除除数,再用出现的余数(第二余数)去除第一余数,如此反复,直到最后余数是0为止,那么最后的除数就是这两个数的最大公约数。(第k余数即第k次计算出现的余数)

int GCD(int a,int b){//辗转相除法   被除数/除数=商
	if(a%b==0) return b;//这里的a b大小不重要,因为即使a<b,到了下面又会换回来
	else return GCD(b,a%b);
}

若要求n个数的最大公约数,可以先求2个数的最大公约数,然后用这两个数的最大公约数和第三个数求最大公约数,然后再用前三个的最大公约数和第四个求最大公约数,以此类推。

2.2最小公倍数

  可以用两个数相乘再除以最大公约数来求得这两个数的最小公倍数(the Least Common Multiple,LCM):

int LCM(int a,int b){//辗转相除法   被除数/除数=商
	return (a*b)/GCD(a,b);
}

求n个数的最小公倍数同上面求n个数的最大公约数。

例题:1979.找出数组的最大公约数(简单)

题目描述:
  给你一个整数数组 nums ,返回数组中最大数和最小数的最大公约数两个数的最大公约数是能够被两个数整除的最大正整数。

输入输出样例:

输入 :nums = [2,5,6,9,10]
输出: 2
解释:
nums 中最小的数是 2
nums 中最大的数是 10
2 和 10 的最大公约数是 2
提示:

  • 2 <= nums.length <= 1000
  • 1 <= nums[i] <= 1000

题解:
  只需要模拟题意,找出来数组中的最大值和最小值(可以用C++内置函数,也可以自己写for循环),然后利用上面的GCD函数即可求出。

int GCD(int a,int b){
     if(a%b==0)return b;
     else return GCD(b,a%b);
}
int findGCD(vector<int>& nums) {
     int n=nums.size();
     int max = *max_element(nums.begin(),nums.end());
     int min = *min_element(nums.begin(),nums.end());
     return GCD(min,max);
}

PIPIOJ例题:1352.多个数的最小公倍数(简单)
  解答见:巧解数学问题——练习题链接及解答

3.筛法和打表

  埃氏筛法是非常常用的判断一个整数是否是质数的方法。用埃氏筛法筛选出n以内的质数:从1到n遍历,假设当前遍历到i,则把所有小于n的且是i的倍数的整数标记为合数;遍历完成后,没有被标记为合数的数字即为质数。时间复杂度为:O(nloglogn)。

q[1]=1;//1为合数
for(int i=2;i<n;i++){
	if(q[i]==0){//q[i]=0表示i为质数
		for(int j=2*i;j<n;j+=i)q[j]=1;
	}
}

例题:204计数质数(中等)

题目描述:
  给定整数n,返回 所有小于非负整数n的质数的数量 。

输入输出样例:

输入 :n = 10
输出: 4
解释:小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。
提示: 0 <= n <= 5*106

题解:
  根据埃氏筛法标记处1~n以内的合数,每标记一次,质数数量减一。

 int countPrimes(int n) {
        if(n<=2)return 0;
        int num=n-2;//去除1和n本身
        vector<int> q(n,0);
        q[1]=1;
        for(int i=2;i<n;i++){
            if(q[i]==0){//q[i]=0表示i为质数
                for(int j=2*i;j<n;j+=i){
                    if(q[j]==0){//避免重复
                    	q[j]=1;
                    	num--;
                    }
                }
            }
        }
        return num;
   }

PIPIOJ例题:1044.七夕节(简单)
  解答见:巧解数学问题——练习题链接及解答

4.进制转换

  任何类型的进制转换都可以参照十进制转二进制的形式,使用短除法进行转换。以下面13和10为例,展示具体转换过程。

13转为二进制10转为二进制
13/2=6…110/2=5…0
6/2=3…05/2=2…1
3/2=1…12/2=1…0
1/2=0…11/2=0…1
11011010

等到商为0的时候结束运算,从下往上把余数连接起来,就得到了最终转换的二进制的值。
例题:504.七进制数(简单)

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

输入输出样例:

输入 :num = 100
输出: “202”
解释:100的七进制表示为:100=2 * 72+0 * 71+2 * 70
提示:

  • -107 <= num <= 107

题解:
  根据短除法,每次记录余数,最后反转字符串即可得到答案。

string convertToBase7(int num) {
        string ans;
        int tmp = abs(num);
        if(num==0)return "0";
        while(tmp){
            int remainder=tmp%7;//记录余数
            ans+='0'+remainder;//将整数转换为字符类型
            tmp/=7;
        }
        reverse(ans.begin(),ans.end());
        return num<0 ? "-"+ans : ans;
}

例题:1017. 负二进制转换(中等)
  解答见:巧解数学问题——练习题链接及解答

5.角度问题(1rad=180°/π)

  关于角度问题,总结以下注意事项:
(1)sin,cos,tan这三个函数使用时都只能用弧度,不能够用角度。
(2)asin,acos,atan这三个函数返回值都为弧度。
(3)PI = acos(-1) , 若OJ题中有用到,请用该公式求π。
例题:PIPIOJ1154.球的半径和体积(简单)

题目描述:
  输入球的中心点和球上某一点的坐标,计算球的半径和体积。

输入输出样例:

输入 :输入包含多组测试用例。对于每一组测试用例,输入球的中心点和球上某一点的坐标,以如下形式输入:x0 y0 z0 x1 y1 z1
输出: 对于每组输入,输出球的半径和体积,结果保留三位小数.
解释:输入:0 0 0 1 1 1 输出:1.732 21.766

题解:
  注意用到π的表达,以及精度问题。球的半径:三维欧几里得距离。球的体积:V=4/3πR3,球的表面积:S=4πR2

#include<bits/stdc++.h> 
using namespace std;
const double PI = acos(-1);
int main(){
    double x0,y0,z0,x1,y1,z1;
    while(scanf("%lf%lf%lf%lf%lf%lf",&x0,&y0,&z0,&x1,&y1,&z1)!=EOF){
        double r,v; 
        r = sqrt((x0-x1)*(x0-x1)+(y0-y1)*(y0-y1)+(z0-z1)*(z0-z1));
        v = 4.0/3 * PI * pow(r,3);
        printf("%.3f %.3f\n",r,v); 
    }
    return 0;
}

6.同余定理

  (1)(a±b)%mod = (a%mod±b%mod)%mod。注意:如果为减法的话a%mod-b%mod可能为负数,所以后面+个mod:(a%mod-b%mod+mod)%mod
  (2)(ab)%mod = (a%modb%mod)%mod。
例题:PIPIOJ1103.PIPI的数学题1(简单)

题目描述:
  PIPI现在有一个很大的数字N,他想知道这个数对p取模的结果。

输入输出样例:

输入 :输入包含多组测试用例。对于每一组测试用例,包含一个正整数N(N<1010000)和一个模数p(0<p<232)
输出: 对于每一组测试用例,输出 N%p
解释:
样例输入:
10 30
30 10
100 33
样例输出:
10
0
1

题解:
  注意输入是一个很大的数,要用字符串读入,利用同余定理即可轻松解决。

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
char s[N];
int main()
{
	long long p;
	while(scanf("%s%lld",s,&p)!=EOF){
		int n=strlen(s);
		long long tmp=0;
		for(int i=0;i<n;i++){
			tmp = (tmp*10+s[i]-'0')%p;
		}
		printf("%lld\n",tmp);
	}
	return 0;
}

7.容斥原理

  (1)两个集合的容斥关系公式:

|A∪B| = |A|+|B| - |A∩B |(∩:重合的部分)

  (2)三个集合的容斥关系公式:

|A∪B∪C| = |A|+|B|+|C| - |A∩B| - |B∩C| - |C∩A| + |A∩B∩C|

例题:PIPIOJ1341.PIPI学容斥(中等)

题目描述:
  给定正整数 n , a, b, c 。让你求出1~n中能够被 a或 b或 c整除的数的个数。

输入输出样例:

输入 :输入包含多组数据。每一组数据包含三个数字 n , a , b , c 。(1<=a, b, c, n <=1e9)。
输出: 对于每一组测试用例,输出1~n中能够被a 或者 b 或者 c整除的数的个数。
解释:
样例输入:
1000 2 3 5
100 2 3 5
样例输出:
734
74

  1~n中能够被a整除的数的个数就是:n/a,能同时被a或者b整除的数的个数就是:n/(a和b的最小公倍数)。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll gcd(ll a, ll b){
	if(a%b==0)return b;
	else return gcd(b,a%b);
}
ll lcm(ll a,ll b){
	return a/gcd(a,b) * b;
}
int main()
{
	ll n,a,b,c;
	while(scanf("%lld%lld%lld%lld",&n,&a,&b,&c)!=EOF){
		ll ans=0;
		ans = n/a + n/b + n/c - n/lcm(a,b)- n/lcm(a,c)- n/lcm(b,c) + n/lcm(a,lcm(b,c));
		printf("%lld\n",ans);
	}
	return 0;
}

8.距离度量公式

  对于平面坐标上的两点(x1,y1)和(x2,y2):
(1)欧式距离计算公式:sqrt((x1-x2)(x1-x2)+(y1-y2)(y1-y2))
(2)曼哈顿距离计算公式:|x1-x2|+|y1-y2| 跟行列的计算顺序无关
例题:PIPIOJ1025: 最短距离(简单)

题目描述:
  小王和小明是好朋友,两人最开始各有一个初始位置 p 和一个恒定速度 v,从0时刻起开始,他们从初始位置以恒定速度开始行走,请告诉我行走过程中两人的最短距离是多少。

输入输出样例:

输入 :
第一行输入T代表测试样例数目。
对于每个样例,第一行包含四个整数 x1,y1,x2,y2,表示人的起点(x1,y1),(x2,y2)。
第二行是四个整数u1,v1,u2,v2,表示人的初始速度(u1,v1),(u2,v2)(分别为x轴和y轴方向的分速度)。
T <= 1000 , x1,y1,x2,y2,u1,v1,u2,v2的绝对值不大于1000。
输出: 对于每个样例,输出一行。“Case i: d”。i 代表案例编号,d代表答案,四舍五入到小数点后6位。
解释:
样例输入:
1
1 1 2 2
1 1 2 2
样例输出:
Case 1: 1.414214

  直接计算两个人的欧氏距离:小王:(x1+u1t,y1+v1t),小明:(x2+u2t,y2+v2t)

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int T;
	scanf("%d",&T);
	int id=1;
	while(T--){
		double x1,x2,y1,y2,v1,v2,u1,u2,ans;
		scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
		scanf("%lf%lf%lf%lf",&u1,&v1,&u2,&v2);
		// ans=sqrt((x1+u1*t-x2-u2*t)^2+(y1+v1*t-y2-v2*t)^2) 
		// 整理得:ans = sqrt(((x1-x2)+(u1-u2)*t)^2+((y1-y2)+(v1-v2)*t)^2) 
		// 化简       =[(u1-u2)^2+(v1-v2)^2]*t^2+ 2*((x1-x2)*(u1-u2)+(y1-y2)*(v1-v2))+(x1-x2)^2+(y1-y2)^2
		double a,b,c;
		a = (u1-u2)*(u1-u2)+(v1-v2)*(v1-v2);
		b = 2*((x1-x2)*(u1-u2)+(y1-y2)*(v1-v2));
		c = (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
		if(a==0||-b/(2*a)<=0)ans=c;
		else ans = c-b*b/(4*a);
		printf("Case %d: %.6f\n",id++,sqrt(ans));
	}
	return 0;
}

9.中位数定理

  现在已知数轴上有n个点 x1,x2 … xn,如何找到一个点x,使得|x-x1|+|x-x2|+…+|x-xn| (或者是|x1-x|+|x2-x|+…+|xn-x|) 的值最小?
  答案:其实这个点x就是这些数的中位数。证明网上有很多,这里就不证明了
例题:PIPIOJ1018: 士兵排阵(中等)

题目描述:
  在一个划分成网格的操场上, n个士兵散乱地站在网格点上。 网格点由整数坐标(x,y)表示。士兵们可以沿网格边上、 下、 左、 右移动一步, 但在同一时刻任一网格点上只能有一名士兵。按照军官的命令,士兵们要整齐地列成一个水平队列,即排列成(x,y),(x+1,y),…,(x+n-1,y)。如何选择 x 和 y的值才能使士兵们以最少的总移动步数排成一列。 计算使所有士兵排成一行需要的最少移动步数。
输入

输入输出样例:

输入 :
多组测试用例。对于每一组测试用例,第1行是士兵数 n, 1≤n≤10000。 接下来 n行是士兵的初始位置, 每行有2个整数 x和 y,-10000≤x, y≤10000。
输出: 数据的输出为一行, 表示士兵排成一行需要的最少移动步数。
解释:
样例输入:
5
1 2
2 2
1 3
3 -2
3 3
样例输出:
8

  该题我们首先应该看出士兵在 x 轴和 y轴的移动是无关的(行列无关)。士兵只能横着移动和竖着移动,所以在 x 轴上的移动和 y 轴上的移动是无关的。所以我们对行列分开考虑。
  假设n个士兵的原始位置为 : (X1,Y1), (X2,Y2), (X3,Y3), (X4,Y4)… (Xn,Yn),士兵的最终位置为: (X,Y), (X+1,Y), (X+2,Y), (X+3,Y), (X+4,Y)… (X+n-1,Y)。
  对于y轴: 先对所有的Y坐标从小到大排序,排序后,为了方便描述,依然用Y1,Y2,Y3…Yn,Yi代表士兵的位置(已经排好序的),假如最后所有士兵都在y轴为Y的坐标上,则变成求|Y1-Y|+|Y2-Y|+|Y3-Y|+……|Yn-1-Y|+|Yn-Y|的最小值,根据中位数定理,我们知道 Y 取 Y1,Y2…Yn中位数时能够得到表达式的最小值。
  对于x轴: 先对所有的X坐标从小到大排序,排序后,为了方便描述,依然用X1,X2,X3…Xn,Xi代表士兵的位置(已经排好序的),最终士兵站的位置为X,X+1,X+2……X+n-1,那么即是求|X1-X|+|X2-(X+1)|+|X3-(X+2)|+……|Xn-(X+n-1)|的最小值。变形,即为|X1-X|+|(X2-1)-X|+|(X3-2)-X|+…+|(Xn-(n-1))-X|,利用中位数定理,X即为X1,X2-1,X3-2……Xn-(n-1)的中位数,据此可以求得X方向的最小移动步数。
  X轴方向移动步数和Y轴方向移动步数加起来即为最终结果。

#include<bits/stdc++.h>
using namespace std;
const int N=10003;
int x[N],y[N];
int main(){
    int n;
    while(scanf("%d",&n)!=EOF){
        int ans=0;
        for(int i=0;i<n;i++) scanf("%d%d",&x[i],&y[i]);
        sort(y,y+n);
        for(int i=0;i<n;i++) ans+=abs(y[i]-y[n/2]);
        sort(x,x+n);
        for(int i=0;i<n;i++) x[i]=x[i]-i;
        sort(x,x+n);
        for(int i=0;i<n;i++) ans+=abs(x[i]-x[n/2]);
        printf("%d\n",ans);
    }
    return 0;
}

每日古诗一首

如梦令·昨夜雨疏风骤
宋·李清照
昨夜雨疏风骤,浓睡不消残酒。试问卷帘人,
却道海棠依旧。知否,知否?应是绿肥红瘦。

——2023.03.23

渔家傲·秋思骤
宋·范仲淹
塞下秋来风景异,衡阳雁去无留意。
四面边生连角起,千嶂里,长烟落日孤城闭。
浊酒一杯家万里,燕然未勒归无计。
羌管悠悠霜满地,人不寐,将军白发征夫泪。

——2023.04.08

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值