作者:小傅哥
博客:https://bugstack.cn
沉淀、分享、成长,让自己和他人都能有所收获!😄
一、前言
数学离程序员有多近?
ifelse也好、for循环也罢,代码可以说就是对数学逻辑的具体实现。所以敲代码的程序员几乎就离不开数学,难易不同而已。
那数学不好就写不了代码吗😳?不,一样可以写代码,可以写出更多的CRUD
出来。那你不要总觉得是产品需求简单所以你的实现过程才变成了增删改查,往往也是因为你还不具备可扩展、易维护、高性能的代码实现方案落地能力,才使得你小小年纪写出了更多的CRUD
!
与一锥子买卖的小作坊相比,大厂和超级大厂更会注重数学能力。
2004年,在硅谷的交通动脉 101 公路上突然出现一块巨大的广告牌,上面是一道数学题: {e 的连续数字中最先出现的 10 位质数}
.com。
广告:这里的 e 是数学常数,自然对数的底数,无限不循环小数。这道题的意思就是,找出 e 中最先出现的 10 位质数,然后可以得出一个网址。进入这个网址会看到 Google 为你出的第二道数学题,成功解锁这步 Google 会告诉你,我们或许是”志同道合“的人
,你可以将简历发到这个邮箱,我们一起做点改变世界的事情。
计算 e 值可以通过泰勒公式推导出来:e^x≈1 + x + x^2/2! + x^3/3! +……+ x^n/n! (1) 推导计算过程还包括埃拉托色尼筛选法(the Sieve of Eratosthenes)
、线性筛选法
的使用。感兴趣的小伙伴可以用代码实现下。
二、编程练习题
1. 斐波那契数列
@Test
public void test_Fibonacci() {
int month = 15; // 15个月
long f1 = 1L, f2 = 1L;
long f;
for (int i = 3; i < month; i++) {
f = f2;
f2 = f1 + f2;
f1 = f;
System.out.println("第" + i + "个月的兔子对数: " + f2);
}
}
- 难度:⭐⭐⭐⭐⭐
- 题目:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?
- 逻辑:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)
- 扩展:斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用,为此,美国数学会从 1963 年起出版了以《斐波纳契数列季刊》为名的一份数学杂志,用于专门刊载这方面的研究成果。
2. 判断素数
@Test
public void test_Prime() {
int count = 0;
for (int i = 101; i < 200; i++) {
boolean b = true;// 默认此数就素数
for (int j = 2; j <= Math.sqrt(i); j++) {
if (i % j == 0) {
b = false; // 此数不是素数
break;
}
}
if (b) {
count++;
System.out.print(i + " ");
}
}
System.out.println("\n素数的个数:" + count);
}
- 难度:⭐⭐⭐
- 题目:判断101-200之间有多少个素数,并输出所有素数。
- 逻辑:判断素数的方法,用一个数分别去除2到sqrt(这个数),如果能被整除,则表明此数不是素数,反之是素数。
- 扩展:素数又称质数,质数的个数是无穷的。欧几里得的《几何原本》中有一个经典的证明。它使用了证明常用的方法:反证法。具体证明如下:假设质数只有有限的n个,从小到大依次排列为p1,p2,……,pn,设N=p1×p2×……×pn,那么, 是素数或者不是素数。
3. 水仙花数
@Test
public void test_narcissus() {
for (int num = 101; num < 1000; num++) {
int bbb = num / 100;
int bb = (num % 100) / 10;
int b = (num % 100) % 10;
if ((bbb * bbb * bbb + bb * bb * bb + b * b * b) == num) {
System.out.println(num);
}
}
}
- 难度:⭐⭐⭐⭐
- 题目:打印出所有的"水仙花数(narcissus number)",所谓"水仙花数"是指一个三位数,其各位数字立方和等于该数本身。例如:153是一个"水仙花数",因为153=1的三次方+5的三次方+3的三次方。
- 逻辑:利用for循环控制100-999个数,每个数分解出个位,十位,百位。
- 扩展:水仙花数(Narcissistic number)也被称为超完全数字不变数(pluperfect digital invariant, PPDI)、自恋数、自幂数、阿姆斯壮数或阿姆斯特朗数(Armstrong number),水仙花数是指一个 3 位数,它的每个位上的数字的 3次幂之和等于它本身(例如:1^3 + 5^3+ 3^3 = 153)。
4. 分解质因数
@Test
public void test_ZhiYinShu() {
f(200);
}
int k = 2;
public void f(int n) {
while (k <= n) {
if (k == n) {
System.out.println(n);
break;
} else if (n > k && n % k ==
System.out.print(k + "*")
n = n / k;
f(n);
break;
} else if (n > k && n % k !=
k++;
f(n);
break;
}
}
}
- 难度:⭐⭐⭐⭐
- 题目:将一个正整数分解质因数。例如:输入90,打印出90=233*5。
- 逻辑:对n进行分解质因数,应先找到一个最小的质数k,然后按此步骤完成(1)如果这个质数恰等于n,则说明分解质因数的过程已经结束,打印出即可。(2)如果n>k,但n能被k整除,则应打印出k的值,并用n除以k的商,作为新的正整数你n,重复执行第一步。(3)如果n不能被k整除,则用k+1作为k的值,重复执行第一步。
- 扩展:每个合数都可以写成几个质数相乘的形式,其中每个质数都是这个合数的因数,把一个合数用质因数相乘的形式表示出来,叫做分解质因数。如30=2×3×5 。分解质因数只针对合数。
5. 杨辉三角
@Test
public void test_YangHuiSanJiao(){
int[][] a = new int[10][10];
for (int i = 0; i < 10; i++) {
a[i][i] = 1;
a[i][0] = 1;
}
for (int i = 2; i < 10; i++) {
for (int j = 1; j < i; j++) {
a[i][j] = a[i - 1][j - 1] + a[i - 1][j];
}
}
for (int i = 0; i < 10; i++) {
for (int k = 0; k < 2 * (10 - i) - 1; k++) {
System.out.print(" ");
}
for (int j = 0; j <= i; j++) {
System.out.print(a[i][j] + " ");
}
System.out.println();
}
}
- 难度:⭐⭐⭐⭐
- 题目:打印出杨辉三角形
- 逻辑:杨辉三角形性质:每行数字左右对称,由1开始逐渐变大,然后变小,回到1。第n行的数字个数为n个。第n行数字和为2^(n-1)。每个数字等于上一行的左右两个数字之和。可用此性质写出整个杨辉三角形。第n行的第1个数为1,第二个数为1×(n-1),第三个数为1×(n-1)×(n-2)/2,第四个数为1×(n-1)×(n-2)/2×(n-3)/3…依此类推。
- 扩展:杨辉三角,是二项式系数在三角形中的一种几何排列,中国南宋数学家杨辉1261年所著的《详解九章算法》一书中出现。在欧洲,帕斯卡(1623----1662)在1654年发现这一规律,所以这个表又叫做帕斯卡三角形。帕斯卡的发现比杨辉要迟393年,比贾宪迟600年。
6. 求最大公约数与最小公倍数
@Test
public void test_Prime() {
int a = 10, b = 24;
int m = division(a, b);
int n = a * b / m;
System.out.println("最大公约数: " + m);
System.out.println("最小公倍数: " + n);
}
public int division(int x, int y) {
int t;
if (x < y) {
t = x;
x = y;
y = t;
}
while (y != 0) {
if (x == y)
return 1;
else {
int k = x % y;
x = y;
y = k;
}
}
return x;
}
- 难度:⭐⭐⭐
- 题目:两个正整数m和n,求其最大公约数和最小公倍数。
- 逻辑:在循环中,只要除数不等于0,用较大数除以较小的数,将小的一个数作为下一轮循环的大数,取得的余数作为下一轮循环的较小的数,如此循环直到较小的数的值为0,返回较大的数,此数即为最小公约数,最小公倍数为两数之积除以最小公倍数。
- 扩展:最大公因数,也称最大公约数、最大公因子,指两个或多个整数共有约数中最大的一个。a,b的最大公约数记为(a,b),同样的,a,b,c的最大公约数记为(a,b,c),多个整数的最大公约数也有同样的记号。求最大公约数有多种方法,常见的有质因数分解法、短除法、辗转相除法、更相减损法。与最大公约数相对应的概念是最小公倍数,a,b的最小公倍数记为[a,b]。两个或多个整数公有的倍数叫做它们的公倍数,其中除0以外最小的一个公倍数就叫做这几个整数的最小公倍数。整数a,b的最小公倍数记为[a,b],同样的,a,b,c的最小公倍数记为[a,b,c],多个整数的最小公倍数也有同样的记号。
7. 完全平方数
@Test
public void test_PerfectSquare() {
for (long l = 1L; l < 100000; l++) {
if (Math.sqrt((l + 100)) % 1 == 0) {
if (Math.sqrt((l + 268)) % 1 == 0) {
System.out.println(l + "加100是一个完全平方数,再加168又是一个完全平方数");
}
}
}
}
- 难度:⭐⭐⭐⭐
- 题目:一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少?
- 逻辑:在10万以内判断,先将该数加上100后再开方,再将该数加上268后再开方,如果开方后的结果满足如下条件,即是结果。
- 扩展:完全平方指用一个整数乘以自己例如11,22,3*3等,依此类推。若一个数能表示成某个整数的平方的形式,则称这个数为完全平方数。完全平方数是非负数,而一个完全平方数的项有两个。注意不要与完全平方式所混淆。
8. 求主对角线之和
@Test
public void test_Sum() {
Scanner s = new Scanner(System.in);
int[][] a = new int[3][3];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
a[i][j] = s.nextInt();
}
}
System.out.println("输入的3 * 3 矩阵是:");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
System.out.print(a[i][j] + " ");
}
System.out.println();
}
int sum = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == j) {
sum += a[i][j];
}
}
}
System.out.println("对角线和是 " + sum);
}
- 难度:⭐⭐⭐
- 题目:求一个3*3矩阵对角线元素之和
- 逻辑:利用双重for循环控制输入二维数组,再将ai累加后输出。
- 扩展:在一个n阶方阵(或是n阶行列式)中,从左上角到右下角这一斜线方向上的n 个元素所在的对角线,叫做n 阶方阵(或行列式)的主对角线。
9. 完数求解
@Test
public void test_solution() {
System.out.println("1到1000的完数有: ");
for (int i = 1; i < 1000; i++) {
int t = 0;
for (int j = 1; j <= i / 2; j++) {
if (i % j == 0) {
t = t + j;
}
}
if (t == i) {
System.out.print(i + " ");
}
}
}
- 难度:⭐⭐⭐⭐
- 题目:一个数如果恰好等于它的因子之和,这个数就称为 "完数 "。例如6=1+2+3.编程 找出1000以内的所有完数
- 逻辑:如果p是质数,且2^p-1也是质数,那么(2^p-1)X2^(p-1)便是一个完全数。
- 扩展:完全数(Perfect number),又称完美数或完备数,是一些特殊的自然数。它所有的真因子(即除了自身以外的约数)的和(即因子函数),恰好等于它本身。
10. 求s=a+aa+aaa+aaaa+aa...a的值
@Test
public void test_asum() {
long a = 2, b = 0;
Scanner s = new Scanner(System.in);
int n = s.nextInt();
int i = 0;
long sum = 0;
while (i < n) {
b = b + a;
sum = sum + b;
a = a * 10;
++i;
}
System.out.println("input number: " + n);
System.out.println(sum);
}
- 难度:⭐⭐⭐
- 题目:求s=a+aa+aaa+aaaa+aa...a的值,其中a是一个数字。例如2+22+222+2222+22222(此时共有5个数相加),几个数相加有键盘控制。
- 逻辑:定义一个变量b, 赋初值为0;定义一变量sum, 赋初值为0,进入循环后,将a + b 的值赋给b,将sum + b 的值赋给sum;同时,将a 增加十倍, ++ i; 继续循环;循环结束后,输出sum 的值。
11. 无重复三位数
@Test
public void test_AC() {
int count = 0;
for (int x = 1; x < 5; x++) {
for (int y = 1; y < 5; y++) {
for (int z = 1; z < 5; z++) {
if (x != y && y != z && x != z) {
count++;
System.out.print(x * 100 + y * 10 + z + " ");
if (count % 4 == 0) {
System.out.println();
}
}
}
}
}
System.out.println("共有" + count + "个三位数");
}
- 难度:⭐⭐⭐⭐
- 题目:有1、2、3、4个数字,能组成多少个互不相同且无重复数字的三位数?都是多少?
- 逻辑:可填在百位、十位、个位的数字都是1、2、3、4。组成所有的排列后再去掉不满足条件的排列。
12. 从小到大输出数列
public class SmallToBig {
public static void main(String[] args) {
SmallToBig fnc = new SmallToBig();
int a, b, c;
System.out.println("Input 3 numbers:");
a = fnc.input();
b = fnc.input();
c = fnc.input();
if (a > b) {
int t = a;
a = b;
b = t;
}
if (a > c) {
int t = a;
a = c;
c = t;
}
if (b > c) {
int t = b;
b = c;
c = t;
}
System.out.println(a + " " + b + " " + c);
}
public int input() {
int value = 0;
Scanner s = new Scanner(System.in);
value = s.nextInt();
return value;
}
public void compare(int x, int y) {// 此方法没用
if (x > y) {
int t = x;
x = y;
y = t;
}
}
}
- 难度:⭐⭐
- 题目:输入三个整数x,y,z,请把这三个数由小到大输出
- 逻辑:办法把最小的数放到x上,先将x与y进行比较,如果x> y则将x与y的值进行交换,然后再用x与z进行比较,如果x> z则将x与z的值进行交换,这样能使x最小。
13. 猴子吃桃问题
public class Monkey {
public static void main(String[] args) {
int lastdayNum = 1;
for (int i = 2; i <= 10; i++) {
lastdayNum = (lastdayNum + 1) * 2;
}
System.out.println("猴子第一天摘了 " + lastdayNum + " 个桃子");
}
}
- 难度:⭐⭐⭐⭐
- 题目:猴子第一天摘下若干个桃子,当即吃了一半,还不瘾,又多吃了一个 第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下 的一半零一个。到第10天早上想再吃时,见只剩下一个桃子了。求第一天共摘了多少。
- 逻辑:采取逆向思维的方法,从后往前推断。
14. 乒乓球比赛
public class Compete {
static char[] m = { 'a', 'b', 'c' };
static char[] n = { 'x', 'y', 'z' };
public static void main(String[] args) {
for (int i = 0; i < m.length; i++) {
for (int j = 0; j < n.length; j++) {
if (m[i] == 'a' && n[j] == 'x') {
continue;
} else if (m[i] == 'a' && n[j] == 'y') {
continue;
} else if ((m[i] == 'c' && n[j] == 'x')
|| (m[i] == 'c' && n[j] == 'z')) {
continue;
} else if ((m[i] == 'b' && n[j] == 'z')
|| (m[i] == 'b' && n[j] == 'y')) {
continue;
} else
System.out.println(m[i] + " vs " + n[j]);
}
}
}
}
- 难度:⭐⭐⭐⭐
- 题目:两个乒乓球队进行比赛,各出三人。甲队为a,b,c三人,乙队为x,y,z三人。已抽签决定比赛名单。有人向队员打听比赛的名单。a说他不和x比,c说他不和x,z比,请编程序找出三队赛手的名单。
15. 求分数之和
public class FenShu {
public static void main(String[] args) {
int x = 2, y = 1, t;
double sum = 0;
DecimalFormat df = new DecimalFormat("#0.0000");
for (int i = 1; i <= 20; i++) {
sum += (double) x / y;
t = y;
y = x;
x = y + t;
System.out.println("第 " + i + " 次相加,和是 " + df.format(sum));
}
}
}
- 难度:⭐⭐⭐⭐
- 题目:有一分数序列:2/1,3/2,5/3,8/5,13/8,21/13...求出这个数列的前20项之和。
- 逻辑:抓住分子与分母的变化规律。
16. 求阶乘的和
public class JieCheng {
static long sum = 0;
static long fac = 0;
public static void main(String[] args) {
long sum = 0;
long fac = 1;
for (int i = 1; i <= 10; i++) {
fac = fac * i;
sum += fac;
}
System.out.println(sum);
}
}
- 难度:⭐⭐⭐
- 题目:求1+2!+3!+...+20!的和
- 逻辑:把累加变成了累乘
- 扩展:阶乘是基斯顿·卡曼(Christian Kramp,1760~1826)于 1808 年发明的运算符号,是数学术语。一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1。自然数n的阶乘写作n!。1808年,基斯顿·卡曼引进这个表示法。
17. 回文判断
public class HuiWen {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
System.out.print("请输入一个正整数:");
long a = s.nextLong();
String ss = Long.toString(a);
char[] ch = ss.toCharArray();
boolean is = true;
int j = ch.length;
for (int i = 0; i < j / 2; i++) {
if (ch[i] != ch[j - i - 1]) {
is = false;
}
}
if (is == true) {
System.out.println("这是一个回文数");
} else {
System.out.println("这不是一个回文数");
}
}
}
- 难度:⭐⭐⭐
- 题目:一个5位数,判断它是不是回文数。即12321是回文数,个位与万位相同,十位与千位相同。
18. 按顺序输出数列
public class ShunXu {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
int a = s.nextInt();
int b = s.nextInt();
int c = s.nextInt();
if (a < b) {
int t = a;
a = b;
b = t;
}
if (a < c) {
int t = a;
a = c;
c = t;
}
if (b < c) {
int t = b;
b = c;
c = t;
}
System.out.println("从大到小的顺序输出:");
System.out.println(a + " " + b + " " + c);
}
}
- 难度:⭐⭐⭐
- 题目:输入3个数a,b,c,按大小顺序输出
19. 位置替换
public class TiHuan {
static final int N = 8;
public static void main(String[] args) {
int[] a = new int[N];
Scanner s = new Scanner(System.in);
int index1 = 0, index2 = 0;
System.out.println("please input numbers");
for (int i = 0; i < N; i++) {
a[i] = s.nextInt();
System.out.print(a[i] + " ");
}
int max = a[0], min = a[0];
for (int i = 0; i < a.length; i++) {
if (a[i] > max) {
max = a[i];
index1 = i;
}
if (a[i] < min) {
min = a[i];
index2 = i;
}
}
if (index1 != 0) {
int temp = a[0];
a[0] = a[index1];
a[index1] = temp;
}
if (index2 != a.length - 1) {
int temp = a[a.length - 1];
a[a.length - 1] = a[index2];
a[index2] = temp;
}
System.out.println("after swop:");
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
}
}
- 难度:⭐⭐
- 题目:输入数组,最大的与第一个元素交换,最小的与最后一个元素交换,输出数组。
20. 1的个数
long startTime = System.currentTimeMillis();
int num = 10000000, saveNum = 1, countNum = 0, lastNum = 0;
int copyNum = num;
while (num != 0) {
lastNum = num % 10;
num /= 10;
if (lastNum == 0) {
// 如果是0那么正好是少了一次所以num不加1了
countNum += num * saveNum;
} else if (lastNum == 1) {
// 如果是1说明当前数内少了一次所以num不加1,而且当前1所在位置
// 有1的个数,就是去除当前1最高位,剩下位数,的个数。
countNum += num * saveNum + copyNum % saveNum + 1;
} else {
// 如果非1非0.直接用公式计算
// abcd...=(abc+1)*1+(ab+1)*10+(a+1)*100+(1)*1000...
countNum += (num + 1) * saveNum;
}
saveNum *= 10;
}
System.out.println("1的个数:" + countNum);
System.out.println("计算耗时:" + (System.currentTimeMillis() - startTime) + "毫秒");
- 难度:⭐⭐⭐⭐
- 题目:1~n中,1出现的次数。比如:1~10,1出现了两次。
- 逻辑:我们能发现这个1的个数在100、1000、10000中是有规则的循环出现的。11、12、13、14或者21、31、41、51,以及单个的1出现。最终可以得出通用公式:abcd...=(abc+1)1+(ab+1)10+(a+1)100+(1)1000...,abcd代表位数。另外在实现的过程还需要考虑比如不足100等情况,例如98、1232等。
三、深度扩展
Java 代码本身就是基于数据结构和算法对数学逻辑的具体实现,而那些隐含在代码中的数学知识如果你不会,那么压根你就会忽略掉它,也就因此看不懂源码了。
就像我问你:
- HashCode为什么用31作为乘数,你证明过吗?
- 扰动函数的函数作用是什么,它还有什么场景在用?
- 拉链寻址和开放寻址具体是什么表现,怎么解决的碰撞问题?
- ThreadLocal 的实现中还有黄金分割点的使用,你知道吗?
- CLH、MCS,都是怎么实现的公平锁,代码是什么样?
- jvmti 可以用于非入侵的监控线程池状态,你用过吗?
所以小傅哥整理了一本,《Java 面经手册》 是一本以面试题为入口讲解 Java 核心技术的 PDF 书籍,书中内容也极力的向你证实代码是对数学逻辑的具体实现
。为什么这么说? 当你仔细阅读书籍时,会发现这里有很多数学知识,包括:扰动函数、负载因子、拉链寻址、开放寻址、斐波那契(Fibonacci)散列法还有黄金分割点的使用等等。
Java 面经手册,资料下载:https://codechina.csdn.net/MiddlewareDesign/doc/-/issues/8
四、总结
- Programming is one of the most difficult branches of applied mathematics; the poorer mathematicians had better remain pure mathematicians. https://www.cs.utexas.edu/use...
- 单纯的只会数学写不了代码,能写代码的不懂数学只能是CRUD码农。数学知识帮助你设计数据结构和实现算法逻辑,代码能力帮你驾驭设计模式和架构模型。多方面的知识结合和使用才是码农和工程师的主要区别,也是是否拥有核心竞争力的关键点。
- 学习知识有时候看不到前面的路有多远,但哪怕是个泥坑,只要你不停的蠕动、折腾、翻滚,也能抓出一条泥鳅。
知识的路上是发现知识的快乐,还学会知识的成就感,不断的促使你前行
。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。