作者:小傅哥
博客: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);
}
复制代码
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 + "个三位数");
}
复制代码
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 + " 个桃子");
}
}
复制代码
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));
}
}
}
复制代码
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);
}
}
复制代码
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("这不是一个回文数");
}
}
}
复制代码
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/users/EWD/transcriptions/EWD04xx/EWD498.html
单纯的只会数学写不了代码,能写代码的不懂数学只能是 CRUD 码农。数学知识帮助你设计数据结构和实现算法逻辑,代码能力帮你驾驭设计模式和架构模型。多方面的知识结合和使用才是码农和工程师的主要区别,也是是否拥有核心竞争力的关键点。
学习知识有时候看不到前面的路有多远,但哪怕是个泥坑,只要你不停的蠕动、折腾、翻滚,也能抓出一条泥鳅。知识的路上是发现知识的快乐,还学会知识的成就感,不断的促使你前行
。
五、系列推荐
评论