1.6-1.15力扣数学刷题

【1】60. 排列序列

日期:1.6

1.题目链接:60. 排列序列 - 力扣(LeetCode)https://leetcode.cn/problems/permutation-sequence/description/?envType=problem-list-v2&envId=math

2.类型:数学

3.方法:数学 + 缩小问题规模(官方题解)

不使用回溯生成所有排列,而是通过数学计算直接确定每一位的数字。

关键概念:

阶乘数组factorial[i] 存储 i! 的值

数字分组 :对于第i位,以某个数字开头的排列有 (n-i)!

确定顺序 :通过 k / (n-i)! 计算当前位应该取剩余数字中的第几个

关键代码:

cpp 复制代码
         for(int i=1;i<=n;++i){
            // factorial[n-i]:剩余(n-i)个数字的全排列数量
            int order=k/factorial[n-i]+1;            
            for(int j=1;j<=n;++j){
                // 如果数字j可用,order减1
                order-=valid[j];
                // 当order减到0时,说明找到了第order个可用数字
                if(!order){
                    ans+=(j+'0');  // 将数字转换为字符加入结果
                    valid[j]=0;      // 标记该数字为已使用
                    break;
                }
            }

【2】70. 爬楼梯

日期:1.7

1.题目链接:70. 爬楼梯 - 力扣(LeetCode)https://leetcode.cn/problems/climbing-stairs/description/?envType=problem-list-v2&envId=math

2.类型:数学,动态规划

3.方法一:动态规划(半解)

这是一个典型的斐波那契数列问题:

爬到第1阶:1种方法(爬1阶)

爬到第2阶:2种方法(1+1或直接爬2阶)

爬到第i阶:可以从第(i-1)阶爬1阶上来,或从第(i-2)阶爬2阶上来

关键代码:

cpp 复制代码
        int p=0,q=0,r=1;  // p: dp[i-2], q: dp[i-1], r: dp[i]        
        for(int i=1;i<=n;++i){
            p=q;      // p 更新为上一轮的 q (dp[i-2])
            q=r;      // q 更新为上一轮的 r (dp[i-1])
            r=p+q;  // r 更新为 p + q (dp[i] = dp[i-1] + dp[i-2])
        }

4.方法二:通项公式(半解)

核心思想:斐波那契数列的通项公式

对于爬楼梯问题:

爬到第0阶:1种方法(不爬)→ 对应 F(1) = 1

爬到第1阶:1种方法 → 对应 F(2) = 1

爬到第2阶:2种方法 → 对应 F(3) = 2

爬到第n阶:对应 F(n+1)

关键代码:

cpp 复制代码
        // 计算√5的值,斐波那契数列通项公式中需要用到
        double sqrt5=sqrt(5);        
        // 使用斐波那契数列的通项公式直接计算第n+1项
        // 注意:爬楼梯问题的解是斐波那契数列的第n+1项
        double fibn=pow((1+sqrt5)/2,n+1)pow((1-sqrt5)/2,n+1);        
        // 将结果除以√5并四舍五入,然后转换为整数
        return (int)round(fibn/sqrt5);

【3】69. x 的平方根

日期:1.8

1.题目链接:69. x 的平方根 - 力扣(LeetCode)https://leetcode.cn/problems/sqrtx/description/?envType=problem-list-v2&envId=math

2.类型:数学,二分查找

3.方法一:袖珍计算器算法(半解)

核心思想:利用指数和对数的性质

根据数学公式:sqrt(x) = x^(1/2) = e^(0.5 * ln(x))

其中:ln(x) 是自然对数(以e为底),e^(y) 是指数函数

计算步骤:

计算 log(x) - 自然对数

乘以 0.5 - 相当于开平方

计算 exp(...) - 指数函数,得到平方根的近似值

转换为整数并修正误差

关键代码:

cpp 复制代码
        if(x==0){
            return 0;
        }        
        // 使用数学公式:sqrt(x) = exp(0.5 * log(x))
        int ans=exp(0.5*log(x));       
        // 使用long long类型避免乘法溢出
        return((long long)(ans+1)*(ans+1)<=x?ans+1:ans);

【4】89. 格雷编码

日期:1.9

1.题目链接:89. 格雷编码 - 力扣(LeetCode)https://leetcode.cn/problems/gray-code/description/?envType=problem-list-v2&envId=math

2.类型:数学,位运算

3.方法一:位运算(官方题解)

核心思想:镜像反射法

格雷码可以通过递归镜像反射的方式生成:

1位格雷码:0, 1

n位格雷码可以通过以下方式从(n-1)位格雷码生成:

保留(n-1)位格雷码序列

将(n-1)位格雷码序列逆序

在每个逆序的格雷码前添加一位1(即在最高位加1)

关键代码:

cpp 复制代码
        ret.reserve(1<<n);  // 1<<n = 2^n     
        // 格雷码序列总是以0开始
        ret.push_back(0);        
        // 从1位格雷码开始,逐步构建到n位格雷码
        for(int i=1;i<=n;i++){
            // 获取当前已生成的格雷码数量(即2^(i-1))
            int m=ret.size();            
            for(int j=m-1;j>=0;j--){
                // (1 << (i-1)) 创建第i位为1的二进制数
                // ret[j] | (1 << (i-1)) 将已有格雷码的第i位置为1
                ret.push_back(ret[j] | (1 << (i - 1)));
            }
        }

【5】150. 逆波兰表达式求值

日期:1.10

1.题目链接:89. 格雷编码 - 力扣(LeetCode)https://leetcode.cn/problems/gray-code/description/?envType=problem-list-v2&envId=math

2.类型:数学,栈,数组

3.方法一:栈(一次题解)

核心思想:使用栈计算后缀表达式

逆波兰表达式的计算规则:

  1. 从左到右扫描表达式

  2. 遇到数字就压入栈中

  3. 遇到运算符就从栈中弹出两个数字进行计算

  4. 将计算结果压回栈中

  5. 重复上述过程,直到表达式结束

  6. 栈中最后剩下的数字就是结果

关键代码:

cpp 复制代码
        for(int i=0;i<n;i++){
            string& token = tokens[i];            
            // 如果是数字,压入栈中
            if(isNumber(token)){
                // 将字符串转换为整数并压栈
                stk.push(atoi(token.c_str()));
            }else{
                // 注意:先弹出的是右操作数,后弹出的是左操作数
                int num2=stk.top();  // 右操作数
                stk.pop();
                int num1=stk.top();  // 左操作数
                stk.pop();                
                // 根据运算符进行计算,并将结果压回栈中
                switch(token[0]){
                    case '+':
                        stk.push(num1+num2);
                        break;
                    case '-':
                        stk.push(num1-num2);
                        break;
                    case '*':
                        stk.push(num1*num2);
                        break;
                    case '/':
                        stk.push(num1/num2);  
                        break;
                }
            }
        }
        return stk.top();
    }

【6】189. 轮转数组

日期:1.11

1.题目链接:189. 轮转数组 - 力扣(LeetCode)https://leetcode.cn/problems/rotate-array/description/?envType=problem-list-v2&envId=math

2.类型:数学,双指针,数组

3.方法一:使用额外的数组(一次题解)

可以使用额外的数组来将每个元素放至正确的位置。用 n 表示数组的长度,遍历原数组,将原数组下标为 i 的元素放至新数组下标为 (i+k)modn 的位置,最后将新数组拷贝至原数组即可。

关键代码:

cpp 复制代码
        vector<int> newArr(n);         // 创建一个新的数组,大小与原数组相同        
        for(int i=0;i<n;++i){
            // 计算元素在新数组中的位置:(i + k) % n
            newArr[(i+k)%n]=nums[i];
        }        
        nums.assign(newArr.begin(), newArr.end());
    }

【7】204. 计数质数

日期:1.12

1.题目链接:204. 计数质数 - 力扣(LeetCode)https://leetcode.cn/problems/count-primes/description/?envType=problem-list-v2&envId=math

2.类型:数学,枚举

3.方法一:枚举(一次题解)

核心思想:逐个判断每个数是否为质数

对于每个数i(2 ≤ i < n),检查它是否为质数

如果是质数,计数器加1

质数判断方法:

要判断一个数x是否为质数,只需检查它是否能被2到√x之间的任何整数整除:

如果x能被其中任何一个数整除,则x不是质数

如果x不能被任何数整除,则x是质数

关键代码:

cpp 复制代码
        bool isPrime(int x){
        // 遍历从2到sqrt(x)的所有数
        for(int i=2;i*i<=x;++i){
            // 如果x能被i整除,则x不是质数
            if(x%i==0){
                return false;
            }
        }

【8】223. 矩形面积

日期:1.13

1.题目链接:223. 矩形面积 - 力扣(LeetCode)https://leetcode.cn/problems/rectangle-area/description/?envType=problem-list-v2&envId=math

2.类型:数学,几何

3.方法一:计算重叠面积(半解)

每个矩形由两个点表示:

(ax1, ay1):矩形A的左下角坐标

(ax2, ay2):矩形A的右上角坐标

同样,(bx1, by1)(bx2, by2)表示矩形B

注意:题目保证ax1 < ax2ay1 < ay2bx1 < bx2by1 < by2

关键代码:

cpp 复制代码
          // 计算两个矩形的面积
        int area1=(ax2-ax1)*(ay2-ay1);
        int area2=(bx2-bx1)*(by2-by1);      
        // 计算重叠部分的宽度和高度
        int overlapWidth=min(ax2, bx2)-max(ax1, bx1);
        int overlapHeight=min(ay2, by2)-max(ay1, by1);        
        // 计算重叠部分的面积(如果没有重叠,则面积为0)
        int overlapArea=max(overlapWidth, 0)*max(overlapHeight, 0);     
        // 总面积 = 两个矩形面积之和 - 重叠部分面积
        return area1+area2-overlapArea;

【9】233. 数字 1 的个数

日期:1.14

1.题目链接:233. 数字 1 的个数 - 力扣(LeetCode)https://leetcode.cn/problems/number-of-digit-one/description/?envType=problem-list-v2&envId=math

2.类型:数学,枚举

3.方法一:逐位计算(官方题解)

对于第k位(从0开始计数,个位是第0位):

设当前位数为10^k(代码中的mulk

假设我们有一个数abcdefg,当前处理百位(k=2,mulk=100

高位部分:abc(即n / (mulk * 10) = n / 1000

当前位:d(即(n % 1000) / 100

低位部分:efg(即n % 100

当前位为1的三种情况:

情况1:高位小于abc(0到abc-1),高位可以取0到abc-1,共abc种,每种高位,当前位固定为1

低位可以取0到99(共mulk=100种)

贡献:abc * 100

对应代码:(n / (mulk * 10)) * mulk

n / (mulk * 10)就是高位abc

乘以mulk(即100)

情况2:高位等于abc,当前位等于1,此时数字形式为abc1efg,贡献的低位范围:0到efg,共efg+1

情况3:高位等于abc,当前位大于1

此时当前位为1时,低位可以取0到99(共100种)

通用公式:

当前位为1的个数 =(高位数字) * 当前位数 +min(max(低位数字 + 1, 0), 当前位数) [当当前位数字=1时] 或当前位数 [当当前位数字>1时]

合并为一个公式:

count = (n / (10 * mulk)) * mulk + min(max(n % (10 * mulk) - mulk + 1, 0), mulk)

关键代码:

cpp 复制代码
         // 循环处理每一位(个位、十位、百位...)
        for(int k=0;n>=mulk;++k){
            // 公式分为两部分:
            // 1. (n / (mulk * 10)) * mulk
            // 2. min(max(n % (mulk * 10) - mulk + 1, 0LL), mulk)
            ans+=(n/(mulk*10))*mulk + 
                   min(max(n % (mulk * 10) - mulk + 1, 0LL), mulk);
            mulk*=10;
        }

【10】279. 完全平方数

日期:1.15

1.题目链接:279. 完全平方数 - 力扣(LeetCode)https://leetcode.cn/problems/perfect-squares/description/?envType=problem-list-v2&envId=math

2.类型:数学,动态规划

3.方法一:动态规划(一次题解)

状态定义:f[i] 表示组成数字 i 所需的最少完全平方数的个数。

状态转移方程:

对于每个 i,我们考虑所有可能的完全平方数 j*j(其中 j*j ≤ i):

f[i] = min(f[i], f[i - j*j] + 1) 对于所有满足 j*j ≤ i 的 j

其中 f[i - j*j] + 1 表示:如果使用 j*j 这个完全平方数,那么剩下的部分 i - j*j 需要 f[i - j*j] 个完全平方数,再加上当前的 j*j 这一个。

关键代码:

cpp 复制代码
        for(int i=1;i<=n;i++){
            int minn=INT_MAX;              
            // 遍历所有小于等于 i 的完全平方数
            for(int j=1;j*j<=i;j++){
                // 如果使用 j*j 这个完全平方数,那么剩余部分为 i - j*j
                minn=min(minn, f[i-j*j]);
            }            
            // 最终 f[i] 等于找到的最小值加 1(加上当前这个完全平方数)
            f[i]=minn+1;
        }
        return f[n];
相关推荐
jiang_bluetooth1 小时前
channel sounding基于探测序列的时延和相位差算法
算法·蓝牙测距·channel sound·gfsk·蓝牙6.0
踩坑记录2 小时前
leetcode hot100 53.最大子数组和 动态规划 medium
leetcode·动态规划
地平线开发者2 小时前
征程 6 算法工具链 | PTQ 深度使用指南
算法·自动驾驶
Xの哲學2 小时前
Linux 软中断深度剖析: 从设计思想到实战调试
linux·网络·算法·架构·边缘计算
暴风游侠2 小时前
如何进行科学的分类
笔记·算法·分类
leaves falling3 小时前
冒泡排序(基础版+通用版)
数据结构·算法·排序算法
老鼠只爱大米3 小时前
LeetCode算法题详解 56:合并区间
leetcode·并查集·合并区间·区间合并·线性扫描·算法面试
蜗牛去旅行吧3 小时前
面试宝典集锦
面试·职场和发展
C雨后彩虹3 小时前
无向图染色
java·数据结构·算法·华为·面试