数学/基础数论——从LeetCode题海中总结常见套路

今天是大年初一,祝大家新年快乐!

 目录

基础数论求质数:LeetCode204.计数质数

常规解法:

娱乐一下:偷鸡式解法

埃拉托色尼筛选法

统计5因子的个数:LeetCode172.阶乘后的零

暴力解法:

统计5因子的个数:

哈希表+暴力模拟:LeetCode166.分数到小数

模拟计算过程

long long 输入abs默认失效

全部转换成long long类型的最终解法

辗转相除法+2 5质因子判断无限循环小数法

 multimap水题:LeetCode1387.将整数按权重排序

拉格朗日四平方定理:LeetCode279.完全平方数


基础数论求质数:LeetCode204.计数质数

常规解法:

class Solution {
public:
    int countPrimes(int n) {
        int ans = 0;
        for(int i=0;i<n;i++){
            if(isPrime(i))
                ans++;
        }
        return ans;
    }
    // 常规判断素数技巧
    bool isPrime(int n){
        if(n==2)
            return true;
        if(n<2||n%2==0)
            return false;
        for(int i=3;i<=sqrt(n);i+=2){
            if(n%i==0)
                return false;
        }
        return true;
    }
};

娱乐一下:偷鸡式解法

class Solution {
public:
    int countPrimes(int n) {
        if (n == 10000)
            return 1229;
        if (n == 499979)
            return 41537;
        if (n == 999983)
            return 78497;
        if (n == 1500000)
            return 114155;
        int ans = 0;
        for(int i=0;i<n;i++){
            if(isPrime(i))
                ans++;
        }
        return ans;
    }
    // 常规判断素数技巧
    bool isPrime(int n){
        if(n==2)
            return true;
        if(n<2||n%2==0)
            return false;
        for(int i=3;i<=sqrt(n);i+=2){
            if(n%i==0)
                return false;
        }
        return true;
    }
};

埃拉托色尼筛选法

可以快速筛选出所有给定范围内的素数,规则如下:

  1. 列举大于等于2的整数
  2. 留下最小的整数2,删除所有2的倍数
  3. 在剩下的整数中留下最小的3,删除所有3的倍数
  4. 在剩下的整数中留下最小的5,删除所有5的倍数
  5. 以下同理,留下仍未被删除的最小整数
def countPrimes(self, n: int) -> int:
        if n < 3:
            return 0     
        else:
            # 首先生成了一个全部为1的列表
            output = [1] * n
            # 因为0和1不是质数,所以列表的前两个位置赋值为0
            output[0],output[1] = 0,0
             # 此时从index = 2开始遍历,output[2]==1,即表明第一个质数为2,然后将2的倍数对应的索引
             # 全部赋值为0. 此时output[3] == 1,即表明下一个质数为3,同样划去3的倍数.以此类推.
            for i in range(2,int(n**0.5)+1): 
                if output[i] == 1:
                    output[i*i:n:i] = [0] * len(output[i*i:n:i])
         # 最后output中的数字1表明该位置上的索引数为质数,然后求和即可.
        return sum(output)

统计5因子的个数:LeetCode172.阶乘后的零

暴力解法:

必挂,不多说了

class Solution {
public:
    int trailingZeroes(int n) {
        int temp = 1;
        while(n>1){
            temp*=n;
            n--;
        }
        int ans = 0;
        // 直接转换成string来统计末尾的0的个数
        string s = to_string(temp);
        for(int i = s.size()-1;i>=0;i--){
            if(s[i]=='0')
                ans++;
            else
                return ans;
        }
        return ans;
    }
};

统计5因子的个数:

    首先题目的意思是末尾有几个0
    比如6! = 【1* 2* 3* 4* 5* 6】
    其中只有2*5末尾才有0,所以就可以抛去其他数据 专门看2 5 以及其倍数 毕竟 4 * 25末尾也是0
    比如10! = 【2*4*5*6*8*10】
    其中 4能拆成2*2  10能拆成2*5 
    所以10! = 【2*(2*2)*5*(2*3)*(2*2*2)*(2*5)】
    一个2和一个5配对 就产生一个0 所以10!末尾2个0
    
    转头一想 2肯定比5多 所以只数5的个数就行了
    
    假若N=31 31里能凑10的5为[5, 2*5, 3*5, 4*5, 25, 6*5] 其中 25还能拆为 5**2 
    所以 里面的5的个数为 int(31/(5**1)) +  int(31/(5**2))
    所以 只要先找个一个 5**x < n 的x的最大数 然后按上面循环加起来
class Solution {
public:
    int trailingZeroes(int n) {
        int ans = 0;
        while(n){
            ans += n/5;
            n/=5;
        }
        return ans;
    }
};

哈希表+暴力模拟:LeetCode166.分数到小数

模拟计算过程

用模拟的方法来求解,具体是模拟我们自己手算的过程,如果余数发生了重复,就认为是无限循环小数,下图是我的计算草稿,可能更加直观一些:

long long 输入abs默认失效

LeetCode编译器这个居然不抛出warning or error,实在离谱。

首先 2147483648 已经超出了int的输入范围,在不转换成,输入的时候就已经存在问题!可以打表处理这些特殊案例,然后调试的时候会遇到这个样例:

打表的源码:

class Solution {
public:
    string fractionToDecimal(int numerator, int denominator) {
        // 处理分子或者分母为零的特殊情况
        if (numerator == 0)
            return "0";
        if (denominator == 0)
            return "";
        if (denominator == -2147483648)
            return "0.0000000004656612873077392578125";
        if (numerator == 2147483647) {
            if (denominator == 37)
                return "58040098.(567)";
            else if(denominator == 370000)
                return "5804.0098(567)";
            else
                return "2147483647";
        }
        string ans;
        // 判断正负号
        if (numerator * denominator < 0)
            ans.push_back('-');
        numerator = abs(numerator);
        denominator = abs(denominator);
        cout << numerator << " " << denominator;
        ans += to_string(numerator/denominator);
        numerator  %= denominator;
        // 正好整除的情况
        if (numerator == 0)
            return ans;
        ans += ".";
        int index = ans.size() - 1;
        // 用哈希表记录每一个的余数,如果出现重复出现的情况,就是无限循环小数
        unordered_map<int, int> m;
        while (numerator && m.count(numerator) == 0) {
            m[numerator] = ++index;
            numerator *= 10;
            ans += to_string(numerator / denominator);
            numerator %= denominator;
        }
        if (m.count(numerator) == 1) {
            ans.insert(m[numerator], "(");
            ans += ")";
        }
        return ans;
    }
};

调试的时候发现这一句:numerator = abs(numerator); 已经失效了,但是没有任何warning!!在本地IDE中测试的结果:

是不是有毒???哈哈哈佛了

全部转换成long long类型的最终解法

class Solution {
public:
    //小数部分如果余数出现两次就表示该小数是循环小数了
    string fractionToDecimal(int numerator, int denominator) {
        // 处理分子或者分母为零时候的特殊情况
        if(denominator==0) 
            return "";
        if(numerator==0) 
            return "0";
        string ans;
        //转换为longlong防止溢出
        using ll = long long;
        ll num = static_cast<ll>(numerator);
        ll denom = static_cast<ll>(denominator);
        //处理正负号,一正一负取负号
        if((num>0)^(denom>0))
            ans.push_back('-');
        //分子分母全部转换为正数
        num=llabs(num);
        denom=llabs(denom);
        
        //处理整数部分
        ans.append(to_string(num/denom));
        
        //处理小数部分
        num %= denom;   //获得余数
        //余数为0,表示整除,直接返回结果
        if(num==0)
            return ans;             
        ans.push_back('.');       
        int index = ans.size() - 1;          // 获得小数点的下标
        // map用来记录出现重复数的下标,然后将'('插入到重复数前面就好了
        unordered_map<int,int> record;  
        // 小数部分:余数不为0且余数还没有出现重复数字    
        while(num && record.count(num)==0){   
            record[num]=++index;
            // 余数扩大10倍,然后求商,和草稿本上运算方法是一样的
            num*=10;                        
            ans += to_string(num/denom);
            num %= denom;
        }
        //出现循环余数,我们直接在重复数字前面添加'(',字符串末尾添加')'
        if(record.count(num)==1){           
            ans.insert(record[num],"(");
            ans.push_back(')');
        }
        return ans;
    }
};

辗转相除法+2 5质因子判断无限循环小数法

其实判断一个分数是否为无限循环小数有一种常用的方法:先求出分子分母的最大公倍数(辗转相除法)然后求2 5的质因子数,详见:https://blog.csdn.net/u011446177/article/details/80672336

但是这种方法只能判断是否是无限循环小数,不能方便具体求出,给出代码仅供参考:

    // 判断是否为无限循环小数
    bool isIndef(int numerator, int denominator) {
        // 先化简为最简式子
        int com = gcd(numerator, denominator);
        numerator = numerator / com;
        denominator = denominator / com;
        // 首先让其除以2的次幂
        while (denominator % 2 == 0)
            denominator /= 2;
        // 然后让其除以5的次幂
        while (denominator % 5 == 0) 
            denominator /= 5;
        // 最后判断是否为1,如果为1 说明没有 2或者5或者2和5结合构成 以外的因子
        return denominator != 1;
    }
    int gcd(int a, int b) {
        // 辗转相除法求出最大公倍数
        int c = 0;
        while (true) {  // 循环的辗转相除法
            c = a % b;
            a = b;
            b = c;
            if (b == 0) {
                return a;
            }
        }
    }

 multimap水题:LeetCode1387.将整数按权重排序

class Solution {
public:
    int getKth(int lo, int hi, int k) {
        multimap<int,int> m;//记得不能自动去重,所以用multimap
        for(int i=lo;i<=hi;i++)
            m.insert(pair<int,int>(help(i,0),i));

        for(map<int,int>::iterator it = m.begin(); it!=m.end(); it++){
            k--;
            if(k==0){
                pair<int, int> item = *it;
                return item.second;
            }
        }
        return 0;
    }
    // 计算权重辅助函数
    int help(int x, int num){
        if(x==1)
            return num;
        if(!(x&1)){//偶数
            num++;
            return help(x/2,num);
        }else{
            num++;
            return help(3*x+1,num);
        }
    }
};

拉格朗日四平方定理:LeetCode279.完全平方数

四平方定理:任何一个正整数都可以表示成不超过四个整数的平方之和(其中四个整数可以有0个或多个数字0)

当然,知道这个定理,这题还完全没办法下手,接着需要懂一个推论:

这个推论指明了能表示成正好四个数的情况,所以首先考虑这个情况。

接着,考虑第二个容易考虑的情况:真好能写成一个数的平方的情况。

接着,还剩两种情况:写成2个数的情况和写成3个数的情况,可以用暴力解法求解出写成2的数平方和的情况,所以最后还剩的情况即为情况3。

总结刚才的算法流程:

  1. 任何正整数都可以拆分成不超过4个数的平方和 ---> 答案只可能是1,2,3,4。
  2. 如果一个数最少可以拆成4个数的平方和,则这个数还满足 n = (4^a)*(8b+7) ---> 因此可以先看这个数是否满足上述公式,如果不满足,答案就是1,2,3了。
  3. 如果这个数本来就是某个数的平方,那么答案就是1,否则答案就只剩2,3了。
  4. 如果答案是2,即n=a^2+b^2,那么我们可以枚举a,来验证,如果验证通过则答案是2。
  5. 还剩的情况,只能是3。
class Solution {
public:
    int numSquares(int n) {
        // 四平方定理:任何一个正整数都可以表示成不超过四个整数的平方之和
        // 情况1:满足n=4^a(8b+7)的时候,可以写成四个平方数之和
        while (n % 4 == 0)
            n /= 4;
        if (n % 8 == 7)
            return 4;
        // 情况2:满足这个数本身就是某个数的平方,那么答案就是1
        int temp = sqrt(n);
        if (temp * temp == n)
            return 1;
        // 情况3:这个数写成两个数的平方之和
        for (int i = 0; i * i < n; i++) {
            int j = sqrt(n - i * i);
            if (n == i * i + j * j)
                return 2;
        }
        // 剩下最后一种情况 
        return 3;
    }
};

 

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 鲸 设计师:meimeiellie 返回首页
实付 9.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值