数学刷题总结
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 解题前思考清单
- 边界条件 :
- 负数、0、末尾0、最大/最小值
- 数据类型溢出风险
- 问题本质 :
- 回文的数学特性:对称性
- 能否利用对称性减少计算量?
- 复杂度分析 :
- 时间:能否从O(n)优化到O(n/2)?
- 空间:能否O(1)?
- 特殊情况 :
- 单数字、双数字
- 奇偶位数差异
4.2 代码优化思考路径
6. 元认知提升要点
- 从"模拟"到"洞察" :
- 不要只满足于解决问题
- 思考问题的数学/逻辑本质
- 提前考虑边界 :
- 养成先列特殊情况的习惯
- 负数、0、溢出是整数问题的常见陷阱
- 空间换时间思考 :
- 这里虽然都是O(1)空间
- 但时间减少了一半,是重要优化
- 测试驱动思考 :
- 12321(奇数位)
- 1221(偶数位)
- 10、100(末尾0)
- -121、0
7. 总结
差距主要在算法洞察力和 边界完整性 。提高方向:
- 多思考问题的数学特性
- 建立系统的边界检查习惯
- 追求最优解,不满足于可行解
- 理解每个优化的理论依据
这种差距的缩小需要: 刻意练习 + 深度思考 + 模式识别 。每次解题后,问自己:"这是最优解吗?还能更好吗?"
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"的特殊性
缩小差距的方法 :
- 解决具体问题前,先分析其特殊性
- 尝试从
结果反推过程,寻找规律 - 写完代码后,问自己:"还能更简单吗?"
- 比较不同解法的核心差异,理解背后的思考方式
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,应返回 2;x=4 时返回 4)。
你的代码错误点主要有2个:
1. 条件判断逻辑写反
你当前的条件是 if ((long) mid * mid >= x),并在满足时更新 ans=mid、right=mid-1,这会导致:
- 当
mid*mid大于等于x时,你错误地把mid记为答案,但此时mid可能是偏大的(比如x=8,mid=3时3*3=9≥8,但正确答案是2,你却会把3暂存为ans)。 - 最终返回的
ans会是“第一个大于等于平方根的数”,而非“最大的小于等于平方根的数”。
2. left 更新逻辑冗余(小问题,但不规范)
left = left + 1 等价于 left++,但更规范的写法是 left = mid + 1(二分查找的标准写法,避免无效循环)。
二、错误代码的执行示例(以x=8为例)
我们走一遍执行流程,就能直观看到问题:
- 初始:
left=0,right=8,ans=0 - 第一次循环:
mid=0+(8-0)/2=4→4*4=16≥8→ans=4,right=3 - 第二次循环:
left=0≤3→mid=0+(3-0)/2=1→1*1=1<8→left=2 - 第三次循环:
left=2≤3→mid=2+(3-2)/2=2→2*2=4<8→left=3 - 第四次循环:
left=3≤3→mid=3+(3-3)/2=3→3*3=9≥8→ans=3,right=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=0,right=8,ans=0 - 第一次循环:
mid=4→16>8→right=3 - 第二次循环:
mid=1→1≤8→ans=1,left=2 - 第三次循环:
mid=2→4≤8→ans=2,left=3 - 第四次循环:
mid=3→9>8→right=2 - 循环结束,返回
2(正确)。
五、补充说明
- 溢出问题:必须把
mid*mid转为long,否则当x=2147395600(接近int最大值)时,mid接近46340,46340*46340=2147395600刚好,但如果mid=46341,46341*46341会超出int范围(int最大值是2147483647),导致计算结果变成负数,条件判断完全错误。 - 特殊值处理:
x=0或x=1时,直接返回自身,减少循环次数,属于优化项(不处理也能正确运行,但更高效)。 - 二分查找的边界逻辑:二分的核心是“缩圈”,每次排除不可能的区间,最终
ans会保留“最大的满足mid*mid ≤x的数”,符合题目“向下取整”的要求。
要彻底理解这个二分循环的终止条件(left > right),我们需要从「循环的目的」「边界收缩的逻辑」「终止时的状态」三个维度拆解,结合具体例子让你一眼看明白。
一、先明确:循环的核心目的
我们的二分是为了在区间 [left, right] 里,找到最大的整数 mid 满足 mid² ≤ x。
循环的每一次迭代,都是在排除不可能的区间,直到区间不存在(left > right),此时所有可能的候选都已排查完毕。
二、终止条件的推导:left <= right 为什么是循环继续的条件?
- 当
left == right时:区间里只剩一个数,必须检查这个数是否满足条件(是最后一次排查); - 当
left > right时:区间「不存在」了(比如left=3,right=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(正确)。
总结终止条件的核心
- 循环继续的条件
left <= right:只要区间里还有数,就继续排查; - 循环终止的条件
left > right:区间消失,所有数已排查完毕; - 终止时的状态:
right是最后一个满足mid² ≤x的数,left是第一个满足mid² >x的数; 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;
}
}

浙公网安备 33010602011771号