LeetCode刷题——数学相关问题

本文涵盖了多种数学和算法问题,包括使用辗转相除法求最大公因数和最小公倍数、计算质数个数的两种方法、进制转换、阶乘尾部零的计算、字符串数字相加、判断是否为3的幂次方、链表随机节点、按权重随机选择、数组元素相乘的排除、最小移动数使数组元素相等、多数元素查找、使用rand7生成rand10等。涉及数据结构、算法、数学和概率等多个领域。
摘要由CSDN通过智能技术生成

数学相关问题

两个数的最大公因数和最小公倍数

思路
使用辗转相除法,可以得到最大公因数(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;
        }
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值