数学刷题总结

9. 回文数

简单把几个用例拿出来,然后先解决大多的用例,然后处理少量用例,问题就解决了。

class Solution {
    public boolean isPalindrome(int x) {
        if (x < 0) {
            return false;
        }

        if (x < 10) {
            return true;
        }

        int y = 0;
        int x1 = x;

        while (x1 > 0) {
            int tmp = x1 % 10;
            x1 = x1 / 10;
            y = y * 10 + tmp;
        }

        return x == y;

    }
}

还是看看官方的题解吧,思路比我这个巧妙

class Solution {
    public boolean isPalindrome(int x) {
        // 特殊情况:
        // 如上所述,当 x < 0 时,x 不是回文数。
        // 同样地,如果数字的最后一位是 0,为了使该数字为回文,
        // 则其第一位数字也应该是 0
        // 只有 0 满足这一属性
        if (x < 0 || (x % 10 == 0 && x != 0)) {
            return false;
        }

        int revertedNumber = 0;
        while (x > revertedNumber) {
            revertedNumber = revertedNumber * 10 + x % 10;
            x /= 10;
        }

        // 当数字长度为奇数时,我们可以通过 revertedNumber/10 去除处于中位的数字。
        // 例如,当输入为 12321 时,在 while 循环的末尾我们可以得到 x = 12,revertedNumber = 123,
        // 由于处于中位的数字不影响回文(它总是与自己相等),所以我们可以简单地将其去除。
        return x == revertedNumber || x == revertedNumber / 10;
    }
}

. 算法元认知能力提升框架

4.1 解题前思考清单

  1. 边界条件
  • 负数、0、末尾0、最大/最小值
  • 数据类型溢出风险
  1. 问题本质
  • 回文的数学特性:对称性
  • 能否利用对称性减少计算量?
  1. 复杂度分析
  • 时间:能否从O(n)优化到O(n/2)?
  • 空间:能否O(1)?
  1. 特殊情况
  • 单数字、双数字
  • 奇偶位数差异

4.2 代码优化思考路径

原始思路 ↓ 识别模式(对称性) ↓ 优化方向(只处理一半) ↓ 处理边界(奇偶、特殊情况) ↓ 验证正确性

6. 元认知提升要点

  1. 从"模拟"到"洞察"
  • 不要只满足于解决问题
  • 思考问题的数学/逻辑本质
  1. 提前考虑边界
  • 养成先列特殊情况的习惯
  • 负数、0、溢出是整数问题的常见陷阱
  1. 空间换时间思考
  • 这里虽然都是O(1)空间
  • 但时间减少了一半,是重要优化
  1. 测试驱动思考
  • 12321(奇数位)
  • 1221(偶数位)
  • 10、100(末尾0)
  • -121、0

7. 总结

差距主要在算法洞察力边界完整性 。提高方向:

  1. 多思考问题的数学特性
  2. 建立系统的边界检查习惯
  3. 追求最优解,不满足于可行解
  4. 理解每个优化的理论依据

这种差距的缩小需要: 刻意练习 + 深度思考 + 模式识别 。每次解题后,问自己:"这是最优解吗?还能更好吗?"

66. 加一

自己做的

class Solution {
    public int[] plusOne(int[] digits) {
        int carry = 1;
        int len = digits.length;
        for (int i = len - 1; i >=0; i--) {
            int tmp = digits[i] + carry;
            if (tmp >= 10) {
                carry = 1;
            } else {
                carry = 0;
            }
            digits[i] = tmp % 10;
            if (carry == 0) {
                return digits;
            }
        }

        int[] res = new int[len + 1];
        res[0] = 1;
        return res;


    }
}

下面是官方的解法,更加的间接,没有那么多变量去判断,理由特殊性,如果发现元素=9,那么置为0即可,否则发现第一个小于9的,则直接+1,然后返回

class Solution {
    public int[] plusOne(int[] digits) {
        int len = digits.length;
        for (int i = len - 1; i >=0; i--) {
            if (digits[i] < 9) {
                digits[i] = digits[i] + 1;
                return digits;
            } else {
                digits[i] = 0;
            }
        }

        int[] res = new int[len + 1];
        res[0] = 1;
        return res;


    }
}

class Solution {
    public int[] plusOne(int[] digits) {
        int n = digits.length;
        for (int i = n - 1; i >= 0; --i) {
            if (digits[i] != 9) {
                ++digits[i];
                for (int j = i + 1; j < n; ++j) {
                    digits[j] = 0;
                }
                return digits;
            }
        }

        // digits 中所有的元素均为 9
        int[] ans = new int[n + 1];
        ans[0] = 1;
        return ans;
    }
}

作者:力扣官方题解
链接:https://leetcode.cn/problems/plus-one/solutions/1057162/jia-yi-by-leetcode-solution-2hor/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

主要差距

  1. 思维抽象层次 :我关注过程模拟,官方关注规律总结
  2. 代码简洁性 :官方解法更精炼
  3. 问题洞察 :官方利用了"只是加1"的特殊性

缩小差距的方法

  1. 解决具体问题前,先分析其特殊性
  2. 尝试从 结果反推过程,寻找规律
  3. 写完代码后,问自己:"还能更简单吗?"
  4. 比较不同解法的核心差异,理解背后的思考方式

172. 阶乘后的零

问题描述:给出一个数,算出阶乘后末尾包含多少个0

我们知道,只有2∗5才可以得到一个0,那我们只需要看7!可以分解为多少个2∗5就可以。

分析:按照n=130为例
第一次除以5时得到26,表明存在26个包含 一 个因数5的数;
第二次除以5得到5,表明存在5个包含 二个因数5的数(这些数字的一个因数5已经在第一次运算的时候统计了);
第三次除以5得到1,表明存在1个包含 三 个因数5的数(这些数字的两个因数5已经在前两次运算的时候统计了);
得到从1-n中所有5的因数的个数

因数(也叫约数)是指能整除一个整数且没有余数的整数。
若整数 a 除以整数 b 的商是整数且没有余数,我们就说:b 是 a 的因数a 是 b 的倍数
对于整数 7,能整除它的正整数只有 1 和 7,这类数被称为质数(素数)

关键性质

  • 任何正整数都至少有两个因数:1 和它本身。
  • 一个数的因数的个数是有限的,而倍数的个数是无限的。
  • 若 a 是 b 的因数,b 是 c 的因数,那么 a 也是 c 的因数(因数的传递性)
class Solution {
    public int trailingZeroes(int n) {
        int ans = 0;
        while(n != 0) {
            n /= 5;
            ans += n;
        }

        return ans;
    }
}

69. x 的平方根

你的这段代码是用来实现“求整数平方根(向下取整)”的,但核心逻辑存在条件判断和结果更新的错误,导致最终返回值不符合题目要求。下面一步步拆解问题,并给出修正方案。

一、核心问题分析

题目要求:给定非负整数 x,返回不超过 x 平方根的最大整数(例如 x=8 时,平方根是≈2.828,应返回 2x=4 时返回 4)。

你的代码错误点主要有2个:

1. 条件判断逻辑写反

你当前的条件是 if ((long) mid * mid >= x),并在满足时更新 ans=midright=mid-1,这会导致:

  • mid*mid 大于等于 x 时,你错误地把 mid 记为答案,但此时 mid 可能是偏大的(比如 x=8mid=33*3=9≥8,但正确答案是 2,你却会把 3 暂存为 ans)。
  • 最终返回的 ans 会是“第一个大于等于平方根的数”,而非“最大的小于等于平方根的数”。

2. left 更新逻辑冗余(小问题,但不规范)

left = left + 1 等价于 left++,但更规范的写法是 left = mid + 1(二分查找的标准写法,避免无效循环)。

二、错误代码的执行示例(以x=8为例)

我们走一遍执行流程,就能直观看到问题:

  • 初始:left=0right=8ans=0
  • 第一次循环:
    mid=0+(8-0)/2=44*4=16≥8ans=4right=3
  • 第二次循环:
    left=0≤3mid=0+(3-0)/2=11*1=1<8left=2
  • 第三次循环:
    left=2≤3mid=2+(3-2)/2=22*2=4<8left=3
  • 第四次循环:
    left=3≤3mid=3+(3-3)/2=33*3=9≥8ans=3right=2
  • 循环结束,返回 3(但正确答案是 2)。

三、修正后的代码

核心思路:

  • mid*mid ≤ x 时,说明 mid候选答案,先记录下来,再尝试找更大的候选(left=mid+1);
  • mid*mid > x 时,说明 mid 偏大,缩小右边界(right=mid-1)。
class Solution {
    public int mySqrt(int x) {
        if (x == 0 || x == 1) { // 特殊值优化,避免二分循环
            return x;
        }
        int left = 0;
        int right = x;
        int ans = 0; // 存储最终答案
        while (left <= right) {
            // 计算mid,避免(left+right)/2可能的溢出(虽然x是int,这里影响不大,但养成习惯)
            int mid = left + (right - left) / 2;
            // 用long防止mid*mid溢出int范围
            long square = (long) mid * mid;
            if (square <= x) {
                // mid是候选答案,先记录,再找更大的
                ans = mid;
                left = mid + 1;
            } else {
                // mid偏大,缩小右边界
                right = mid - 1;
            }
        }
        return ans;
    }
}

四、修正后执行示例(x=8)

  • 初始:left=0right=8ans=0
  • 第一次循环:mid=416>8right=3
  • 第二次循环:mid=11≤8ans=1left=2
  • 第三次循环:mid=24≤8ans=2left=3
  • 第四次循环:mid=39>8right=2
  • 循环结束,返回 2(正确)。

五、补充说明

  1. 溢出问题:必须把 mid*mid 转为 long,否则当 x=2147395600(接近int最大值)时,mid 接近46340,46340*46340=2147395600 刚好,但如果 mid=4634146341*46341 会超出int范围(int最大值是2147483647),导致计算结果变成负数,条件判断完全错误。
  2. 特殊值处理x=0x=1 时,直接返回自身,减少循环次数,属于优化项(不处理也能正确运行,但更高效)。
  3. 二分查找的边界逻辑:二分的核心是“缩圈”,每次排除不可能的区间,最终 ans 会保留“最大的满足 mid*mid ≤x 的数”,符合题目“向下取整”的要求。

要彻底理解这个二分循环的终止条件(left > right,我们需要从「循环的目的」「边界收缩的逻辑」「终止时的状态」三个维度拆解,结合具体例子让你一眼看明白。

一、先明确:循环的核心目的

我们的二分是为了在区间 [left, right] 里,找到最大的整数 mid 满足 mid² ≤ x
循环的每一次迭代,都是在排除不可能的区间,直到区间不存在(left > right),此时所有可能的候选都已排查完毕。

二、终止条件的推导:left <= right 为什么是循环继续的条件?

  • left == right 时:区间里只剩一个数,必须检查这个数是否满足条件(是最后一次排查);
  • left > right 时:区间「不存在」了(比如 left=3right=2,没有数能同时≥3且≤2),所有数都排查过了,循环必须终止。

三、终止时的关键状态(核心!)

循环终止时 left > right,此时区间的状态有两个绝对成立的结论:

结论1:所有 ≤ right 的数,都满足 mid² ≤ x

结论2:所有 ≥ left 的数,都满足 mid² > x

这两个结论是由我们的「缩圈规则」决定的,举个例子(x=8):

循环步骤 left right mid mid²与x的关系 缩圈操作 状态说明
初始 0 8 - - - 待排查区间 [0,8]
1 0 8 4 16>8 right=3 排除4~8,剩余[0,3]
2 0 3 1 1≤8 left=2, ans=1 保留1,排查更大的[2,3]
3 2 3 2 4≤8 left=3, ans=2 保留2,排查更大的[3,3]
4 3 3 3 9>8 right=2 排除3,剩余[3,2](区间消失)
终止 3 2 - - - left>right,循环结束

终止时:

  • right=2 → 所有 ≤2 的数(0,1,2)都满足 mid² ≤8
  • left=3 → 所有 ≥3 的数(3,4,...)都满足 mid² >8
  • 因此最大的满足条件的数就是 right=2(或之前记录的 ans=2)。

四、为什么终止时 ans 一定是对的?

ans 的更新规则是:只有当 mid² ≤x 时,才会把 ans 更新为 mid
这意味着:

  • ans 只会记录「满足条件的候选」;
  • 每次更新 ans 都是因为找到了「比之前更大的满足条件的数」(因为 left=mid+1 会往更大的方向找);
  • 循环终止前,最后一次更新的 ans,就是「最大的满足条件的数」。

比如x=8的例子:

  • 第一次更新 ans=1(mid=1);
  • 第二次更新 ans=2(mid=2);
  • 后续mid=3不满足条件,ans 不再更新;
  • 终止时 ans=2,正好是最大的满足条件的数。

五、再举一个极端例子(x=1)

  • 初始:left=0, right=1, ans=0;
  • 第一次循环:mid=0+(1-0)/2=0 → 0²≤1 → ans=0,left=1;
  • 第二次循环:left=1≤1 → mid=1 →1²≤1 → ans=1,left=2;
  • 终止:left=2>right=1,返回ans=1(正确)。

总结终止条件的核心

  1. 循环继续的条件 left <= right:只要区间里还有数,就继续排查;
  2. 循环终止的条件 left > right:区间消失,所有数已排查完毕;
  3. 终止时的状态:right 是最后一个满足 mid² ≤x 的数,left 是第一个满足 mid² >x 的数;
  4. ans 全程记录了最大的满足条件的数,因此终止后返回 ans 必然正确。

这个终止条件是二分查找的通用规则(几乎所有“找最后一个满足条件的元素”的二分都用这个终止条件),记住「终止时 left > right,且 right 是最后一个满足条件的数」,就能应对绝大多数二分边界问题。

50. Pow(x, n)

问题描述:计算x的n次方
思路:借助利用位运算的方式去计算

class Solution {
    public double myPow(double x, int n) {
        if (x == 0) {
            return x;
        }
        if (x == 1) {
            return 1;
        }

        // 特殊值:-1的偶数次幂是1,奇数次幂是-1
        if (x == -1) {
            return n % 2 == 0 ? 1 : -1;
        }

        // 保证exponent是正数
        long exponent = n;
        if (exponent < 0) {
            x = 1 / x;
            exponent = -exponent;
        }

        // 快速幂核心逻辑(二分法)
        double result = 1.0;
        while (exponent > 0) {
            // 如果当前指数是奇数,多乘一次x
            if ((exponent & 1) == 1) {
                result *= x;
            }
            // 指数折半,底数平方
            x *= x;
            exponent >>= 1; // 等价于exponent = exponent / 2
        }

        return result;
    }
}
posted @ 2025-12-17 20:48  coder江  阅读(14)  评论(0)    收藏  举报