基础数论
一、最大公约(因)数(gretest common divisor,gcd)
利用辗转相除法实现
int gcd(int a, int b) {
if (b == 0) {
return a;
} else {
return gcd(b, a % b);
}
}
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
二、最小公倍数(least common multiple,lcm)
依靠最大公约数来求
int lcm(int a, int b) {
return a * b / gcd(a, b);
}
三、质数(是指在大于 1 的自然数中,除了 1 和它本身以外不再有其他因(约)数的自然数。值得注意的是,每一个数都可以分解成质数的乘积。)
四、回文数(注意如何判断回文数)
取长度一半进行比较就可以判别回文数
class Solution {
public boolean isPalindrome(int x) {
String s = Integer.toString(x);
int i = 0;
while (i < s.length() / 2) {
if (s.charAt(i) != s.charAt(s.length() - 1 - i)) {
return false;
}
i++;
}
return true;
}
}
五、丑数
考虑到丑数只能被2、3或(和)5整除,我们可以把当前数除以2 3 5(除的顺序不影响,因为有除法(乘法)交换律,能被整除的才除,不能整除的就跳过),除完之后看剩下的数是不是1即可。
class Solution {
public boolean isUgly(int n) {
if (n <= 0) {
return false;
}
while (n % 2 == 0) n /= 2;
while (n % 3 == 0) n /= 3;
while (n % 5 == 0) n /= 5;
if (n == 1) {
return true;
}
return false;
}
}
六、※砝码称重(进制问题)
注意,题目中没有说砝码可以重复使用,要用尽可能少的砝码组合称出尽可能多的重量,砝码重量是3的指数幂,砝码允许放在左右两个盘中。
以5为例,5写成三进制 = 12 = 1 x 3^1 + 2 x 3^0,我们给第0位+1(加了之后还要减1才能相抵),= 2,-1(三进制),就=2 x 3^1 - 1,因为不能重复使用砝码,所以还得把剩下的2转成1,怎么转呢?一样的方法,加1再减1,=1,-1,-1(三进制),就=1 x 3^2 - 3 - 1 = 5。
注意在实现时,从低位开始遍历,别忘了最后还要生成表达式。
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
// 5 => 12
String str = Integer.toString(n, 3);
// arr = ['2','1'],从低位开始遍历,遇到 2 方便加一减一变换
char[] arr = new StringBuilder(str).reverse().toString().toCharArray();
// list也是从低位开始存储
List<Integer> list = new ArrayList<>();
for (int i = 0; i < arr.length; i++) {
// 因为不能重复使用砝码,所以必须保证每个位最多为 1
if (arr[i] == '2') {
list.add(-1);
// 判断是不是最高位
if (i == arr.length - 1) {
list.add(1);
} else {
arr[i + 1]++;
}
} else if(arr[i] == '3') {
list.add(0);
if (i == arr.length - 1) {
list.add(1);
} else {
arr[i + 1]++;
}
} else {
list.add(arr[i] - '0');
}
}
String ans = "";
for (int i = list.size() - 1; i >= 0; i--) {
int tmp = list.get(i);
if (i == list.size() - 1) {
ans += (int)Math.pow(3, i);
continue;
}
if (tmp > 0) {
ans += "+" + (int)Math.pow(3, i);
}
if (tmp < 0) {
ans += "-" + (int)Math.pow(3, i);
}
}
System.out.println(ans);
}
}
如果把上述3进制,改成2、4进制,还是一样的做法。
七、Nim游戏
就是说轮到谁的时候没有石子取,谁就输了。
结论:把n堆石子10进制数求异或,如果异或结果=0,先手必输,如果异或结果!=0,先手必赢。
八、直线划分平面
(以划分的最优情况来看)一条直线可以把平面划分出2个平面,两条直线可以把平面划分出4个平面,三条直线可以把平面划分出7个平面…
2 = 1 + 1
4 = 1 + 3
7 = 1 + 6
1 3 6是典型的:n*(n+1)/2,所以得到答案:
n
条
直
线
最
多
划
分
出
1
+
n
∗
(
n
+
1
)
2
个
平
面
n条直线最多划分出1 + \frac{n*(n+1)}{2}个平面
n条直线最多划分出1+2n∗(n+1)个平面
九、阶乘结果中末尾有几个0
学习自:https://labuladong.gitee.io/algo/4/30/119/
比如说输入 n = 5,算法返回 1,因为 5! = 120,末尾有一个 0。
只考虑因子5的个数,而不是2的个数,是因为因子2的个数肯定多余5,不用管2。
问题就转换为,找n!可以分解出多少个5?
以125!为例,125 / 5 = 25,意思是有25个5的倍数:5、10、15、20、25…,注意这里125是指1-125个数间有多少个5的倍数。
我们刚刚只考虑了5的倍数,但是25很明显=5 * 5,还可以分一个5出来,所以再看有多少个25的倍数,125 / 25 = 5,有5个25的倍数,这5个25的倍数又可以多提供一个5。再来看125 = 5 * 5 * 5,它是5的倍数,是25的倍数,前面两次只统计了2个5,还有1个5是因为它是125的倍数,所以还要统计125的倍数有几个,125 / 125 = 1,还能额外提供1个5。
注意,上面的125不是单纯的125,而是1-125的正整数。125 / 5 = 25是统计5的倍数有多少个,125 / 25 = 5是统计25的倍数有多少个,以此类推。这种方法也可以用于其它数,例如1-36,有多少个3的倍数,36 / 3 = 12:3 6 9 12 15 18 21 24 27 30 33 36。
有了上面的分析可以写出下列代码:
int n;
int divisor = 5;
int cnt = 0;
while (divisor <= n) {
cnt += n / divisor;
divisor *= 5;
}
乘积尾零
蓝桥杯考了类似的题目,因为因子2的个数一定多余因子5的个数,所以只要去找每个数能够分解成的因子5的个数的和即可。(实在不知道因子2的个数是否多余因子5的个数的话,可以把因子2的个数和与因子5的个数和都求出来,取最小值即可。
第一题也就被解决了,再看下面一道题。
就是找有多少个数的,它们阶乘的尾数0 = k。可以写出下面直观的代码。
int res = 0;
for (int n = 0; n < +inf; n++) {
if (trailingZeroes(n) < K) {
continue;
}
if (trailingZeroes(n) > K) {
break;
}
if (trailingZeroes(n) == K) {
res++;
}
}
return res;
但是时间复杂度高,我们可以使用二分法优化,找到第一个=k的数,再找第一个大于k的数,两者一减就是答案。
class Solution {
public int preimageSizeFZF(int k) {
int left = 0;
int right = Integer.MAX_VALUE;
int mid = 0;
// 先找第一个=k的
int first = binarySearch(left, mid, right, k);
if (first == -1) {
return 0;
}
left = 0;
right = Integer.MAX_VALUE;
mid = 0;
// 再找第一个大于k的
int second = binarySearch1(left, mid, right, k);
System.out.println(first);
return second - first;
}
// 尾数0个数
int check(int n) {
int cnt = 0;
int divisor = 5;
while (divisor <= n) {
cnt += n / divisor;
divisor *= 5;
}
return cnt;
}
int binarySearch1(int left, int mid, int right, int k) {
while (left < right) {
mid = left + (right - left) / 2;
if (check(mid) > k) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
int binarySearch(int left, int mid, int right, int k) {
int ans = -1;
while (left <= right) {
mid = left + (right - left) / 2;
if (check(mid) == k) {
ans = mid;
// 我们要找第一个满足条件的,找到了之后还要往左边走
right = mid - 1;
} else if (check(mid) < k) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return ans;
}
}
十、唯一缺少的数
已经告诉了范围[0,n],那我们可以先用求和公式算出本来的和,再求数组中有的和,相减就是缺失的数字。
class Solution {
public int missingNumber(int[] nums) {
int len = nums.length;
// 有[0,n]中的n个数,找缺的数
int sum = (0 + len) * (len + 1) / 2;
int tmp = 0;
for (int i = 0; i < len; i++) {
tmp += nums[i];
}
return sum - tmp;
}
}
十一、前缀表达式(波兰表达式)、后缀表达式(逆波兰表达式)
x缀表达式,是说运算符全部放在数字前面、中间、后面,其中中缀表达式就是常见的a + b = c的形式,需要知道前缀、后缀表达式如何求值,以及中缀表达式如何转换为前缀、后缀表达式。
11.1 前缀表达式求值
编程解决:
从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素和次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果。
11.2 后缀表达式求值
11.3 中缀表达式转前缀表达式、中缀表达式转后缀表达式
中缀表达式就是最常见的表达式,5 + 2 = 7。
前缀表达式和后缀表达式形成都是对于整个中缀表达式按照运算顺序见运算符就加括号,保证每个运算符外面一定要有括号(原本有就没必要加了),然后每个符号挪到自己那层括号的外面(前缀是前面,后缀是后面),然后去掉括号。
持续更新ing
十二、快速幂运算
//递归快速幂
int quickPow(int a, int n)
{
if (n == 0)
return 1;
else if (n % 2 == 1)
return qpow(a, n - 1) * a;
else
{
int temp = qpow(a, n / 2);
return temp * temp;
}
}