数学相关问题
两个数的最大公因数和最小公倍数
思路
使用辗转相除法,可以得到最大公因数(greatest common divisor,gcd)。将两个数相乘再除以最大公因数即可得到最小公倍数(least common multiple, lcm)。
int gcd(int a,int b){
if(b==0)return a;
return gcd(b,a%b);
}
int lcm(int a,int b){
int g = gcd(a,b);
return a*b/g;
}
204. 质数的个数 (Easy)
问题描述
给定一个数字n,求小于n 的质数的个数。
输入输出样例
示例 1:
输入:n = 10
输出:4
解释:小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。
示例 2:
输入:n = 0
输出:0
示例 3:
输入:n = 1
输出:0
思路
思路1:最简单的方法,从头开始判断一个数是否是质数,是的话就计数。
思路2:俄氏筛选法,创建一个大小为n的数组,先将所有位置置为素数标记。然后从2开始遍历,如果当前i
位置为素数,就从i*i
的位置开始,将所有i
的整数倍的位置置为合数。这样遍历完所有元素时,就完全区分了素数和合数。再遍历一次统计即可。
代码1(超时)
class Solution {
public int countPrimes(int n) {
int count = 0;
for(int i=0;i<n;i++){
if(isPrimes(i))count++;
}
return count;
}
boolean isPrimes(int n){
if(n == 1 || n==0)return false;
for(int i=2;i<=n/2;i++){
if(n%i == 0)return false;
}
return true;
}
}
代码二(俄氏筛选法)
class Solution {
public int countPrimes(int n) {
boolean[] prime = new boolean[n];
Arrays.fill(prime,true); //先将所有位置都标记为素数
for(int i=2;i*i<n;i++){ //遍历到sqrt(n)的位置就能填满,如果遍历到n后面int会溢出
if(prime[i]){
for(int j=i*i;j<n;j+=i){ //关于为什么从i*i开始,因为之前的已经被前面的数填充过了,不需要再填充
prime[j]=false;
}
}
}
int count = 0;
for(int i=2;i<n;i++){
if(prime[i])count++;
}
return count;
}
}
504. 7进制(Easy)
问题描述
给定一个整数 num,将其转化为 7 进制,并以字符串形式输出。
输入输出样例
示例 1:
输入: num = 100
输出: “202”
示例 2:
输入: num = -7
输出: “-10”
思路
进制转换类型的题,通常是利用除法和取模(mod)来进行计算,同时也要注意一些细节,如负数和零。如果输出是数字类型而非字符串,则也需要考虑是否会超出整数上下界。【可作为R进制的模板】
代码
class Solution {
public String convertToBase7(int num) {
if(num == 0)return "0";
boolean is_negative = num<0;
if(is_negative) num = -num; //如果是负数先变为0
String string = "";
while (num!=0){
int b = num%7;
num = num/7;
string = b+string;
}
if(is_negative)return "-"+string;
return string;
}
}
172. 阶乘后的零
问题描述
给定一个整数 n ,返回 n! 结果中尾随零的数量。
提示 n! = n * (n - 1) * (n - 2) * … * 3 * 2 * 1
输入输出样例
示例 1:
输入:n = 3
输出:0
解释:3! = 6 ,不含尾随 0
示例 2:
输入:n = 5
输出:1
解释:5! = 120 ,有一个尾随 0
思路
每个尾部的0 由2 × 5 = 10 而来,因此我们可以把阶乘的每一个元素拆成质数相乘,统计有多少个2 和5。明显的,质因子2 的数量远多于质因子5 的数量,因此我们可以只统计阶乘结果里有多少个质因子5。
代码
class Solution {
public int trailingZeroes(int n) {
int count = 0;
for(int i=5;i<=n;i+=5){
int j = i;
while (j%5 == 0){
count++;
j/=5;
}
}
return count;
}
}
代码二
class Solution {
public int trailingZeroes(int n) {
if (n==0) return 0;
return n/5+trailingZeroes(n/5);
}
}
415. 字符串数字相加 (Easy)
问题描述
给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和并同样以字符串形式返回。
你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。
输入输出样例
示例 1:
输入:num1 = “11”, num2 = “123”
输出:“134”
示例 2:
输入:num1 = “456”, num2 = “77”
输出:“533”
思路
用两个指针分别指向两个字符串末尾,然后使用竖式相加的方式进行两个指针所指数字的相加。注意一种情况:99999+1,要一直进位。如果一个字符比较短,可以在前面补零。
代码
class Solution {
public String addStrings(String num1, String num2) {
int m = num1.length();
int n = num2.length();
int i = m-1,j = n-1;
int pre = 0;//进位
int sum = 0;//两数之和
StringBuilder stringBuilder = new StringBuilder();
while (i>=0 || j>=0 || pre!=0){ //当所有位数都用完,但是进位不为零时两个都补零
int a = i>=0?num1.charAt(i)-'0' : 0; //位数不够补位
int b = j>=0?num2.charAt(j)-'0' : 0;
sum = a+b+pre; //两数相加并加上进位标志位
pre = (a+b+pre)/10; //更新进位
stringBuilder.append(sum%10);
i--;j--;
}
return stringBuilder.reverse().toString();
}
}
326. 三的幂次方 (Easy)
问题描述
给定一个整数,写一个函数来判断它是否是 3 的幂次方。如果是,返回 true ;否则,返回 false 。
整数 n 是 3 的幂次方需满足:存在整数 x 使得 n == 3x
示例 1:
输入:n = 27
输出:true
示例 2:
输入:n = 0
输出:false
思路
可以一直用3除n,如果不满足n%3==0,则返回false。
代码
class Solution {
public boolean isPowerOfThree(int n) {
if(n == 0)return false;
if(n == 1)return true;
if(n%3 != 0)return false;
return isPowerOfThree(n/3);
}
}
class Solution {
public boolean isPowerOfThree(int n) {
if(n == 0)return false;
while (n%3==0){
n = n/3;
}
if(n == 1)return true;
return false;
}
}
代码二
class Solution {
public boolean isPowerOfThree(int n) {
if(n>0) return 1162261467%n == 0; //int范围内,3的最大次幂为1162261467,如果能被该数整除,则n是3的次幂
return false;
}
}
384. 打乱数组 (Medium)
问题描述
输入是一个存有整数数字的数组,和一个包含指令函数名称的数组。输出是一个二维数组,
表示每个指令生成的数组。
输入输出样例
Input: nums = [1,2,3], actions: [“shuffle”,“shuffle”,“reset”]
Output: [[2,1,3],[3,2,1],[1,2,3]]
在这个样例中,前两次打乱的结果只要是随机生成即可。
思路
我们采用经典的Fisher-Yates 洗牌算法,原理是通过随机交换位置来实现随机打乱,有正向和反向两种写法,且实现非常方便。注意这里“reset”函数以及类的构造函数的实现细节。
代码
class Solution {
int[] nums;
public Solution(int[] nums) {
this.nums = nums;
}
public int[] reset() {
return this.nums;
}
public int[] shuffle() {
int n = nums.length;
int[] new_nums = Arrays.copyOf(nums,nums.length);
for(int i=0;i<n;i++){
int rand = (int)(Math.random()*n); //Math.random产生[0,1)范围的随机数
swap(new_nums,i,rand);
}
return new_nums;
}
private void swap(int[] nums,int a,int b){
int tem = nums[a];
nums[a] = nums[b];
nums[b] = tem;
}
}
/**
* Your Solution object will be instantiated and called as such:
* Solution obj = new Solution(nums);
* int[] param_1 = obj.reset();
* int[] param_2 = obj.shuffle();
*/
528. 按权重随机选择 (Medium)
问题描述
给你一个 下标从 0 开始 的正整数数组 w ,其中 w[i] 代表第 i 个下标的权重。
请你实现一个函数 pickIndex ,它可以 随机地 从范围 [0, w.length - 1] 内(含 0 和 w.length - 1)选出并返回一个下标。选取下标 i 的 概率 为 w[i] / sum(w) 。
例如,对于 w = [1, 3],挑选下标 0 的概率为 1 / (1 + 3) = 0.25 (即,25%),而选取下标 1 的概率为 3 / (1 + 3) = 0.75(即,75%)。
输入输出样例
示例 1:
输入:
[“Solution”,“pickIndex”]
[[[1]],[]]
输出:
[null,0]
解释:
Solution solution = new Solution([1]);
solution.pickIndex(); // 返回 0,因为数组中只有一个元素,所以唯一的选择是返回下标 0。
思路
假设一个数组为【2,3,5】,其前缀求和数组为【2,5,10】。随机产生一个0-9的数字,如果是[0 1]返回前缀0,[ 2 3 4]返回前缀1,[5,6,7,8,9]返回前缀2。就相当于权值大的数字,随机时将大的范围数映射到该区域。映射的方式为:返回求和数组第一个大于rand的索引。
代码
class Solution {
int[] sum_nums;
public Solution(int[] w) {
sum_nums = new int[w.length];
sum_nums[0]=w[0];
for(int i=1;i<w.length;i++){
sum_nums[i]=sum_nums[i-1]+w[i];
}
}
public int pickIndex() {
int b = sum_nums[sum_nums.length-1];
int rand = (int)(Math.random()*b);
return search(sum_nums,rand);
}
public int search(int[] sum,int t){
int low=0;
int high = sum.length-1;
while (low<=high){
int mid = low + (high-low)/2;
if(sum[mid]==t)return mid+1;
else if(sum[mid]>t) high = mid-1;
else low = mid+1;
}
return low; //当搜索不存在的数时,结束后tartget在nums[high]和nums[low]之间
}
}
382. 链表随机节点 (Medium)
问题描述
给你一个单链表,随机选择链表的一个节点,并返回相应的节点值。每个节点 被选中的概率一样 。
实现 Solution 类:
Solution(ListNode head)
使用整数数组初始化对象。
int getRandom()
从链表中随机选择一个节点并返回该节点的值。链表中所有节点被选中的概率相等。
思路
先遍历一遍链表,求链表长度len。然后在随机生成一个[0,len)范围的随机数rand,然后指针向后走rand步。
【蓄水池抽样方法:将消耗空间转换为消耗时间(个人感觉思路值得借鉴,但是效率反而不如前面的思路。后面有时间再学)】
class Solution {
int len = 0;
ListNode head;
public Solution(ListNode head) {
this.head = head;
ListNode p = head;
while(p!=null){
len++;
p = p.next;
}
}
public int getRandom() {
Random random = new Random();
int rand = random.nextInt(len);
ListNode p = head;
for(int i=0;i<rand;i++){
p = p.next;
}
return p.val;
}
}
168. Excel Sheet Column Title (Easy)
问题描述
给你一个整数 columnNumber ,返回它在 Excel 表中相对应的列名称。
例如:
A -> 1
B -> 2
C -> 3
…
Z -> 26
AA -> 27
AB -> 28
…
输入输出样例
示例 1:
输入:columnNumber = 1
输出:“A”
示例 2:
输入:columnNumber = 28
输出:“AB”
思路
需要注意的一点是从1 开始而不是0。于是可以将数字-1变为从0开始计数,再将其转换为26进制表示。
class Solution {
public String convertToTitle(int columnNumber) {
StringBuilder stringBuilder = new StringBuilder();
int a = 0;
while (columnNumber>0){
columnNumber-=1; //将数字变到从0开始计数
a = columnNumber%26;
stringBuilder.append(Character.toChars('A'+a));
columnNumber/=26;
}
return stringBuilder.reverse().toString();
}
}
67. Add Binary (Easy)
问题描述
给你两个二进制字符串,返回它们的和(用二进制表示)。
输入为 非空 字符串且只包含数字 1 和 0。
输入输出样例
示例 1:
输入: a = “11”, b = “1”
输出: “100”
示例 2:
输入: a = “1010”, b = “1011”
输出: “10101”
思路
和之前十进制加法求和思路一样。
代码
class Solution {
public String addBinary(String a, String b) {
int m=a.length();
int n=b.length();
int i=m-1,j=n-1;
int pre = 0;
StringBuilder stringBuilder = new StringBuilder();
while (i>=0 || j>=0 || pre>0){
int num1 = i>=0?a.charAt(i)-'0':0;
int num2 = j>=0?b.charAt(j)-'0':0;
int sum = num1+num2+pre;
stringBuilder.append(sum%2);
pre = sum/2;
i--;j--;
}
return stringBuilder.reverse().toString();
}
}
238. Product of Array Except Self (Medium)
问题说明
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。
题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请不要使用除法,且在 O(n) 时间复杂度内完成此题。
输入输出样例
示例 1:
输入: nums = [1,2,3,4]
输出: [24,12,8,6]
示例 2:
输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]
思路
用一个L数组,L[i]
表示i左边数乘积;用一个R数组,R[i]
表示右边数乘积;则输出数组out[i]=L[i]*R[i]
代码
class Solution {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] L = new int[n];
int[] R = new int[n];
L[0]=1;
for(int i=1;i<n;i++){ //L[i]表示i左边数的乘积
L[i]=L[i-1]*nums[i-1];
}
R[n-1]=1;
for(int i=n-2;i>=0;i--){//R[i]表示i右边数的乘积
R[i]=R[i+1]*nums[i+1];
}
for(int i=0;i<n;i++){
nums[i]=L[i]*R[i];
}
return nums;
}
}
462. Minimum Moves to Equal Array Elements II (Medium)
问题描述
给你一个长度为 n 的整数数组 nums ,返回使所有数组元素相等需要的最少移动数。
在一步操作中,你可以使数组中的一个元素加 1 或者减 1 。
输入输出样例
示例 1:
输入:nums = [1,2,3]
输出:2
解释:
只需要两步操作(每步操作指南使一个元素加 1 或减 1):
[1,2,3] => [2,2,3] => [2,2,2]
示例 2:
输入:nums = [1,10,2,9]
输出:16
思路
移动最少的步数需要将所有值都变到中位数的值上去。如果为数组是奇数,直接就是中位数。如果数组是偶数,则将中位数进行向上或者向下取整(这两种方式移动步数一样)。
代码
class Solution {
public int minMoves2(int[] nums) {
Arrays.sort(nums);
int n = nums.length;
int mid = nums[n/2];
int sum = 0;
for(int i=0;i<n;i++){
sum += Math.abs(nums[i]-mid);
}
return sum;
}
}
169. Majority Element (Easy)
问题描述
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
输入输出样例
示例 1:
输入:nums = [3,2,3]
输出:3
示例 2:
输入:nums = [2,2,1,1,1,2,2]
输出:2
思路
选择一个目标阵营,同阵营的人加入,不同阵营的人相互抵消。当人数为零时重新选择一个目标阵营,最后剩下有人的就是目标阵营。
从第一个数开始count=1,遇到相同的就加1,遇到不同的就减1,减到0就重新换个数开始计数,总能找到最多的那个。
代码
class Solution {
public int majorityElement(int[] nums) {
int count = 0;
int target = nums[0];
for(int i=0;i<nums.length;i++){
if(nums[i]==target){
count++;
}else count--;
if(count==0) target = nums[i+1];
}
return target;
}
}
470. Implement Rand10() Using Rand7() (Medium)
问题描述
给定方法 rand7 可生成 [1,7] 范围内的均匀随机整数,试写一个方法 rand10 生成 [1,10] 范围内的均匀随机整数。
你只能调用 rand7() 且不能调用其他方法。请不要使用系统的 Math.random() 方法。
每个测试用例将有一个内部参数 n,即你实现的函数 rand10() 在测试时将被调用的次数。请注意,这不是传递给 rand10() 的参数。
输入输出样例
示例 1:
输入: 1
输出: [2]
示例 2:
输入: 2
输出: [2,8]
思路
参考思路
此题可以扩展为一类题,即使用randN()产生randM()的随机数。假设M大于N。
利用randN()产生randM()存在一种规律,即:(rand_N()-1)*R+rand_R()
可以产生1~NR的均匀的随机数。
反之利用randM()产生randN()存在:rand_M()%N+1
可以产生1-N的均匀的随机数(其中M是N的整数倍)
针对本问题,可以先考虑产生一个1-49的均匀的随机数。但考虑到49不是10的整数倍,可以选择丢弃一部分。使用1-40的随机数生成1-10的随机数。
代码
/**
* The rand7() API is already defined in the parent class SolBase.
* public int rand7();
* @return a random integer in the range 1 to 7
*/
class Solution extends SolBase {
public int rand10() {
while (true){
int a = rand7();
int b = rand7();
int rand = (a-1)*7+b; //产生1-49均匀的随机数
if(rand<=40) return rand%10+1; //rand%10产生0-9的随机数
}
}
}
优化
为了降低不符合条件循环采样的概率,使用不符合条件情况再次筛选采样的方式进行优化:
class Solution extends SolBase {
public int rand10() {
while (true){
int a = rand7();
int b = rand7();
int rand = (a-1)*7+b; //产生1-49均匀的随机数
if(rand<=40) return rand%10+1; //rand%10产生0-9的随机数
a = rand-40; //rand9
b = rand7();
rand = (a-1)*7+b; //产生1-63均匀的随机数
if(rand<=60) return rand%10+1;//rand%10产生0-9的随机数
a = rand-60; //rand3
b = rand7();
rand = (a-1)*7 +b ;//产生1-21的随机数
if(rand<=20) return rand%10+1;//rand%10产生0-9的随机数
}
}
}
202. Happy Number (Easy)
问题描述
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
输入输出样例
示例 1:
输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
示例 2:
输入:n = 2
输出:false
思路
对于一个数n,如果是快乐数则在一定次数的循环以后各个位数平方和为1;如果不是快乐数,它的各个位数平方和的数字会在一个循环内进行重复。于是我们可以用一个Hashset对每个数的平方和进行保存,如果再次出现在HashSet中则说明不是快乐数。
代码
class Solution {
public boolean isHappy(int n) {
Set<Integer> set = new HashSet<>();
while (true){
int s = 0;
while (n>0){
int a = n%10; //当前位数
s += a*a;
n /= 10;
}
if(s == 1)return true;
if(set.contains(s))return false;
set.add(s);
n = s;
}
}
}