【数据结构与算法】数学题经典题总结
- 1.回文数
- 2.计数质数
- 最大公约数解法
- 3.七进制数
- 4.数字转化为十六进制数
- 5.Excel表列名称
- 6.阶乘后的零
- 7.二进制求和与字符串求和
- 8. 最少移动次数使数组元素相等 II
- 9.多数元素
- 10.有效的完全平方数
- 11.3的幂
- 12.除自身以外数组的乘积
- 13.三个数的最大乘积
回文数
LeetCode:回文数
题目描述:
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
示例:
输入: 121
输出: true
思想:
- x%10得到尾数,x/d(d为10的x的位数次方)得到首位数字,比较二者是否相同;
- 注意:循环条件必须是x>0而不是x>10。当x为个位数时,如果是中心位置必然是true,但如果是1000021这种情况(不是回文数),x最后为2,此时x%10跟x/d不相等需要再判断一轮。
20200603又发现一个好方法
将数字的后半部分翻转形成一个新的数字,比较二者大小
注意两点:
- 奇数回文串是比较除10后的大小;
- 注意该方法,末尾为0的情况,会出问题,应该在程序开头把末尾为0的情况排除掉;
代码:
class Solution {
public:
bool isPalindrome(int x) {
if(x<0){
return false;
}
int d = 1,m,n;
while(x/d>=10) d*=10;
while(x>0){
m = x%10;
n = x/d;
if(m == n){
x=(x-m*d)/10;
d=d/100;
}else{
return false;
}
}
return true;
}
};
2020/6/3新方法
class Solution {
public boolean isPalindrome(int x) {
if(x==0) return true;
if(x<0) return false;
int reverse = 0;
while(x>reverse){
reverse = reverse*10+x%10;
x = x/10;
}
return reverse==x||x==reverse/10;
}
}
计数质数
LeetCode:计数质数
题目描述:
统计所有小于非负整数 n 的质数的数量。
示例:
输入: 10
输出: 4
解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。
思想:
三个要点:
- 厄拉多塞筛法:外层循环从2开始遍历,内层循环把2的所有的倍数都划去,3,5类似。最后所有没划去的数都是质数。
- 外层循环终点为Math.sqrt(n)即可,无需遍历完所有比n小的数。因为x*x大于n时,后面所有没划去的数就都是质数了。
- 内层循环从i*i开始,因为所有比i小的数,之前已经遍历过了。例如,i=3时,直接从9开始,因为6在i=2的那一轮已经遍历过了。
代码:
class Solution {
public int countPrimes(int n) {
if(n<3) return 0;
boolean[] arr = new boolean[n];
int count=2;
for(int i=2;i<=Math.sqrt(n);++i){
if(arr[i]) continue;
for(int j=i,k;(k=i*j)<n;++j){
if(arr[k]==false) count++;
arr[k] = true;
}
}
return arr.length-count;
}
}
最大公约数
解法1:辗转相除法
int gcd(int a, int b){
return b == 0 ? a : gcd(b, a % b);
}
缺点:取模运算开销较大
解法2:用减法替代取模
x和y的公约数与x-y和y的公约数相同
int gcd(int a, int b){
if(b > a) return gcd(b, a);
if(b == 0) return a;
return gcd(a - b, b);
}
这个方法虽然免去了大整数除法的繁琐,但是增加了迭代次数。
解法3:利用公约数特性
对于 a 和 b 的最大公约数 f(a, b),有:
- 如果 a 和 b 均为偶数,f(a, b) = 2*f(a/2, b/2);
- 如果 a 是偶数 b 是奇数,f(a, b) = f(a/2, b);
- 如果 b 是偶数 a 是奇数,f(a, b) = f(a, b/2);
- 如果 a 和 b 均为奇数,f(a, b) = f(b, a-b);
偶数除以2的操作,可以用移位运算来完成
int gcd(int a, int b){
if(b > a) return gcd(b, a);
if(b == 0) return a;
boolean aIsEven = isEven(a), bIsEven = isEven(b);
if(aIsEven && bIsEven) return gcd(a>>1 , b>>1)<<1;
if(aIsEven && !bIsEven) return gcd(a>>1 , b);
if(!aIsEven && bIsEven) return gcd(a , b>>1);
return gcd(b, a - b);
}
boolean isEven(int x){
return x%2==0;
}
求最小公倍数
最小公倍数等于两数乘积除以最大公约数
int lcm(int a, int b){
return a * b / gcd(a, b);
}
七进制数
LeetCode:七进制数
题目描述:
给定一个整数,将其转化为7进制,并以字符串形式输出。
示例:
输入: 100
输出: "202"
输入: -7
输出: "-10"
思想:
短除法(除基取余法)进行进制转换。
注意:num为0的情况;num小于0的情况。
代码:
class Solution {
private StringBuilder division(int num){
if(num==0) return new StringBuilder();
return division(num/7).append(num%7);
}
public String convertToBase7(int num) {
if(num==0) return "0";
if(num<0){
StringBuilder res = new StringBuilder("-");
return res.append(division(-num)).toString();
}
return division(num).toString();
}
}
数字转换为十六进制数
LeetCode:数字转换为十六进制数
题目描述:
给定一个整数,编写一个算法将这个数转换为十六进制数。对于负整数,我们通常使用 补码运算 方法。
注意:
十六进制中所有字母(a-f)都必须是小写。
十六进制字符串中不能包含多余的前导零。如果要转化的数为0,那么以单个字符'0'来表示;对于其他情况,十六进制字符串中的第一个字符将不会是0字符。
给定的数确保在32位有符号整数范围内。
不能使用任何由库提供的将数字直接转换或格式化为十六进制的方法。
示例:
输入:
-1
输出:
"ffffffff"
思想:
十六进制比较特殊,可以采用移位的方法来做,与“1111”求与,即可得到右边四位(即一个十六进制位)的数字大小。
代码:
我的笨办法,又垃圾又长
class Solution {
private char[] chars = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
private ArrayList<Integer> hex(int num){
if(num==0){
return new ArrayList<Integer>();
}
ArrayList<Integer> arr = hex(num/16);
arr.add(num%16);
return arr;
}
private StringBuilder toStr(ArrayList<Integer> arr){
StringBuilder str = new StringBuilder();
Iterator<Integer> it = arr.iterator();
while(it.hasNext()){
str.append(chars[it.next()]);
}
return str;
}
public String toHex(int num) {
if(num==0) return "0";
if(num>0) return toStr(hex(num)).toString();
ArrayList<Integer> arr = hex((-2>>>1) + num +1);
if(arr.isEmpty()) return "80000000";
arr.set(0,arr.get(0)+8);
return toStr(arr).toString();
}
}
标准做法
class Solution {
private char[] chars = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
public String toHex(int num) {
if(num==0) return "0";
StringBuilder str = new StringBuilder();
while(num!=0){
str.append(chars[num & 0b1111]);
num>>>=4;
}
return str.reverse().toString();
}
}
Excel表列名称
LeetCode:Excel表列名称
题目描述:
给定一个正整数,返回它在 Excel 表中相对应的列名称。
例如,
1 -> A
2 -> B
3 -> C
...
26 -> Z
27 -> AA
28 -> AB
...
示例:
输入: 701
输出: "ZY"
思想:
注意这题跟普通的进制转化不太一样,相当于n-1之后取26进制。进制转换是从0开始的,这题是从1开始。
代码:
class Solution {
private StringBuilder convert(int n){
if(n<=26){
StringBuilder str = new StringBuilder();
return str.appendCodePoint(n+64);
}
StringBuilder str = convert((n-1)/26);
str.appendCodePoint((n-1)%26+65);
return str;
}
public String convertToTitle(int n) {
return convert(n).toString();
}
}
阶乘后的零
LeetCode:阶乘后的零
题目描述:
给定一个整数 n,返回 n! 结果尾数中零的数量。
示例:
输入: 5
输出: 1
解释: 5! = 120, 尾数中有 1 个零.
思想:
0是由 2 * 5 产生的。2 的倍数远多于 5 的倍数。因此只考虑阶乘中所有 5 的倍数的数量。对于一个数 n ,小于等于 n 的5的倍数数量为 n/5 。那这个 m=n/5 是不是就是答案。当然不是了,像 25 这种,可以产生两个 0 。因此需要加上25的倍数数量。同理,需要加上 125倍数数量等等。所以结果应该等于 n/5 + n/25 + n/125 +...递归或者迭代都可以实现。
代码:
class Solution {
public int trailingZeroes(int n) {
if(n<1) return 0;
return n/5 + trailingZeroes(n/5);
}
}
二进制求和
LeetCode:二进制求和
题目描述:
给你两个二进制字符串,返回它们的和(用二进制表示)。
输入为 非空 字符串且只包含数字 1 和 0。
示例:
输入: a = "1010", b = "1011"
输出: "10101"
思想:
- 维护一个carry,加1加1加1;
- 注意下次计算carry取 0 或1,用carry/2来表示;
代码:
我的笨方法(转化为码点数组来做非常耗时)
class Solution {
public String addBinary(String a, String b) {
int[] A = a.codePoints().toArray();
int[] B = b.codePoints().toArray();
StringBuilder res = new StringBuilder();
int i=a.length()-1,j = b.length()-1;
int add =0,m;
for(;i>=0||j>=0;--i,--j){
m=(i<0?0:(A[i]-48)) +(j<0?0:(B[j]-48)) + add;
if(m>=2){
add = 1;
res.appendCodePoint(m%2+48);
continue;
}
res.appendCodePoint(m+48);
add = 0;
}
if(add==1) res.append('1');
return res.reverse().toString();
}
}
标准做法
class Solution {
public String addBinary(String a, String b) {
StringBuilder res = new StringBuilder();
int i=a.length()-1,j = b.length()-1;
int carry=0;
for(;carry==1||i>=0||j>=0;--i,--j){
if(i>-1 && a.charAt(i) == '1')
carry++;
if(j>-1 && b.charAt(j) == '1')
carry++;
res.append(carry%2);
carry = carry/2;
}
return res.reverse().toString();
}
}
此外还有一题“字符串求和”,做法类似
class Solution {
public String addStrings(String num1, String num2) {
StringBuilder res = new StringBuilder();
int i=num1.length()-1,j=num2.length()-1;
int carry=0;
while(i>-1||j>-1||carry>0){
if(j>-1) carry+=num2.charAt(j--) - '0';
if(i>-1) carry+=num1.charAt(i--) - '0';
res.append(carry%10);
carry /=10;
}
return res.reverse().toString();
}
}
最少移动次数使数组元素相等2
LeetCode:最少移动次数使数组元素相等2
题目描述:
给定一个非空整数数组,找到使所有数组元素相等所需的最小移动数,其中每次移动可将选定的一个元素加1或减1。 您可以假设数组的长度最多为10000。
示例:
输入:
[1,2,3]
输出:
2
说明:
只有两个动作是必要的(记得每一步仅可使其中一个元素加1或减1):
[1,2,3] => [2,2,3] => [2,2,2]
思想:
- 中位数就是目标元素;可以画图理解。例如有两个元素相邻,x 和 y (设y-x=z),x左边有 m 个元素,y右边有 n 个元素,若取x 则结果要增加 mz ,若取y 则结果增加nz;若 m>n,则一定是取 y 更佳;以此类推,取中位数是最佳目标。如果有连续相等元素也不影响。
- 先排序再计算。计算结果时,可以用双指针,可以少遍历一半的路程。
代码:
class Solution {
public int minMoves2(int[] nums) {
Arrays.sort(nums);
int l=0,h=nums.length-1,res=0;
while(h>l){
res += nums[h--]-nums[l++];
}
return res;
}
}
多数元素
LeetCode:多数元素
题目描述:
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例:
输入: [2,2,1,1,1,2,2]
输出: 2
思想:
摩尔投票算法(Boyer-Moore majority vote algorithm):用于寻找相同数量大于n/2的元素。
算法基于:每次从序列里选择两个不相同的数字删除掉(或称为“抵消”),最后剩下一个数字或几个相同的数字,就是出现次数大于总数一半的那个。
用cnt记录相同元素的数量,如果出现不同元素则减一,相同加一;cnt变成0时,更新 m 为当前元素。最后 m 的结果一定是数量最多的那个。
注意:若没有元素数量大于 n/2 结果可能不能准确
代码:
class Solution {
public int majorityElement(int[] nums) {
int majority=0,cnt=0;
for(int item : nums){
majority = (cnt == 0)? item : majority;
cnt = majority==item?(cnt+1):(cnt-1);
}
return majority;
}
}
有效的完全平方数
LeetCode:有效的完全平方数
题目描述:
给定一个正整数 num,编写一个函数,如果 num 是一个完全平方数,则返回 True,否则返回 False。
说明:不要使用任何内置的库函数,如 sqrt。
示例:
输入:16
输出:True
思想:
我首先想到的是最蠢的方法:i=1,i++遍历,比较i*i与num。这样会超时,应该从完全平方数的规律来考虑。
完全平方数的性质:
1,4,9,16,25可以发现互相之间的差为1,3,5,7,9,即相差 2 的等差数列,或者说奇数序列。因此可以想到下面的方法一。
注意:如果不用num--,而是取一个m,按照这个规律叠加再比较m和num的大小,这样可能会超出int的范围
牛顿迭代法
这方法看完之后,不禁直呼牛逼。具体看下面的链接:
367题解
这就是数学的力量。
其实还可以用二分查找法来做。为啥我就想不到呢。
代码:
普通方法:
class Solution {
public boolean isPerfectSquare(int num) {
int m = 1;
while(num>0){
num -= m;
m += 2;
}
return num==0;
}
}
牛顿迭代法:
class Solution {
public boolean isPerfectSquare(int num) {
if(num==1) return true;
long x = num/2;
while(x*x>num){
x = (x + num/x)/2;
}
return x*x==num;
}
}
3的幂
LeetCode:3的幂
题目描述:
给定一个整数,写一个函数来判断它是否是 3 的幂次方。
示例:
输入: 27
输出: true
思想:
3的幂次的质因子只有3,int范围最大的3次幂,即1162261467的因子只可能是3的幂次,因此1162261467%n==0即可判断n是否为3的幂次。
代码:
class Solution {
public boolean isPowerOfThree(int n) {
return n>0&&1162261467%n==0;
}
}
除自身以外数组的乘积
LeetCode:除自身以外数组的乘积
题目描述:
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
进阶:
你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)
示例:
输入: [1,2,3,4]
输出: [24,12,8,6]
思想:
每一个项的结果等于左边乘积*右边乘积,因此做两次循环,第一次记录每一项的左边乘积,第二次在乘上右边乘积。
代码:
class Solution {
public int[] productExceptSelf(int[] nums) {
int len = nums.length;
int[] res = new int[len];
Arrays.fill(res,1);
int left=1;
for(int i=0;i<len-1;++i){
left *= nums[i];
res[i+1] *= left;
}
int right=1;
for(int i=len-1;i>0;--i){
right *= nums[i];
res[i-1] *= right;
}
return res;
}
}
三个数的最大乘积
LeetCode:三个数的最大乘积
题目描述:
给定一个整型数组,在数组中找出由三个数组成的最大乘积,并输出这个乘积。
注意:
给定的整型数组长度范围是[3,10^4],数组中所有的元素范围是[-1000, 1000]。
输入的数组中任意三个数的乘积不会超出32位有符号整数的范围。
示例:
输入: [1,2,3,4]
输出: 24
思想:
循环取三个最大值方法:
if(num>max1){
max3 = max2;
max2 = max1;
max1 = num;
}else if(num>max2){
max3 = max2;
max2 = num;
}else if(num>max3){
max3=num;
}
注意:可能存在负数的情况。最小的两个负数相乘再乘上最大的正数,可能是最终结果。
代码:
class Solution {
public int maximumProduct(int[] nums) {
int max1=Integer.MIN_VALUE,max2=Integer.MIN_VALUE,max3=Integer.MIN_VALUE;
int min1=Integer.MAX_VALUE,min2=Integer.MAX_VALUE;
for(int num : nums){
if(num>max1){
max3 = max2;
max2 = max1;
max1 = num;
}else if(num>max2){
max3 = max2;
max2 = num;
}else if(num>max3){
max3=num;
}
if(num<min1){
min2 = min1;
min1 = num;
}else if(num<min2){
min2 = num;
}
}
return Math.max(max1*max2*max3,max1*min1*min2);
}
}