力扣刷题日常(9-10)(待完善)
力扣刷题日常(9-10)
第9题: 回文数 (难度: 简单)
原题:
给你一个整数 x
,如果 x
是一个回文整数,返回 true
;否则,返回 false
。
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
- 例如,
121
是回文,而123
不是。
示例 1:
输入:x = 121
输出:true
示例 2:
输入:x = -121
输出:false
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入:x = 10
输出:false
解释:从右向左读, 为 01 。因此它不是一个回文数。
提示:
-2^31 <= x <= 2^31 - 1
**进阶:**你能不将整数转为字符串来解决这个问题吗?
原题提示(附翻译):
Beware of overflow when you reverse the integer.(反转整数时要小心溢出。)
开始解题:
思路1: 转换为字符串
这是很容易就能想到的方法.我们把数字变成一串字符, 然后检查这串字符是不是一个回文串.那回文串在第5题里也有过涉及.
- 转换: 将整数 x 转换为字符串
- 反转: 将这个字符串反转
- 比较: 比较原始字符串与反转字符串是否相等. 如果相等, 这就是回文数.
这个方法很简单直接,但看题目的进阶部分: “你能不将整数转为字符串来解决这个问题吗?”.这通常是面试官更想看到的解法,因为它更能考察我们对数字本身的操作能力
思路2: 数学解法 - 反转一半数字
这个办法完全通过数学运算来处理,避免了字符串转换带来的额外内存开销,效率更高. 核心思想是: 我们只反转数字的后半部分,然后把它和前半部分进行比较.
那为什么我们只反转一半呢? 题目的提示中提到了"反转时当心溢出". 如果我们尝试反转一个完整的非常大的整数, 反转后的整数可能会超过 int 的显示范围,导致程序出错. 只反转一半可以完美地规避这个问题.
我们用 1221
这个栗子来分解步骤:
-
处理特殊情况(边缘处理):
- 负数: 负数绝对不是回文数,从原题给的例子里我们就能看出来. 因为负号只在最左边. 反转之后负号在最右边. 所以只要输入 x 为负数,直接返回
false
. - 以0结尾的非0数: 如过一个数的最后一位是 0 (例如 10, 120),那么它想成为回文数,第一位就必须是 0. 但除了 0 本身,其他任何数字都不能以 0 开头. 所以,任何以 0 结尾且不为 0 的数都不是回文数,直接返回
false
.
- 负数: 负数绝对不是回文数,从原题给的例子里我们就能看出来. 因为负号只在最左边. 反转之后负号在最右边. 所以只要输入 x 为负数,直接返回
-
核心反转逻辑:
- 我们创建一个新变量
revertedNumber
来存储反转后的后半部分数字,初始值为 0. - 我们用一个循环,每次循环都从原数字 x 中取出最后一位,加到
revertedNumber
的末尾,同时将 x 的最后一位移除. - 循环的终止条件是
x<=revertedNumber
. 当 x 不再大于revertedNumber
时, 说明我们已经处理到数字的中心部分了.
以
1221
为栗子:- 初始:
x=1221
,revertedNumber=0
- 循环1:
- 取出
x
的末位:1221 % 10
->1
- 更新
revertedNumber
:revertedNumber = revertedNumber * 10 + 1
->1
- 更新
x
:x = 1221 / 10
->122
- 此时
x(122) > revertedNumber(1)
,循环继续
- 取出
- 循环2:
- 取出
x
的末位:122 % 10
->2
- 更新
revertedNumber
:revertedNumber = revertedNumber * 10 + 2
->12
- 更新
x
:x = 122 / 10
->12
- 此时
x(12) == revertedNumber (12)
,循环停止.
- 取出
- 我们创建一个新变量
-
最后比较:
- 循环结束后,我们需要比较
x
(原数字的前半部分) 与revertedNumber
(原数字反转后的后半部分). - 对于偶数位数的数字(如
1221
): 循环结束后,x
会等于revertedNumber
, 所以判断x== revertedNumber
- 对于奇数位数的数字(如
121
):- 初始:
x = 121
,revertedNumber = 0
- 循环1:
x = 12
,revertedNumber = 1
- 循环2:
x = 1
,revertedNumber = 12
. 此时x(1) <revertedNumber(12)
, 循环停止. - 在这种情况下,
x = 1
,revertedNumber = 12
. 中间的那个2
被包含在了revertedNumber
里. 为了公平比较, 我们需要把这个中间位去掉, 即revertedNumber / 10
(12 / 10
->1
). 然后再比较x
和revertedNumber / 10
.
- 初始:
- 所以, 最终的判断条件是:
x == revertedNumber || x == revertedNumber / 10
. 这个条件同时覆盖了偶数位数和奇数位数的情况.
- 循环结束后,我们需要比较
代码实现:
public class Solution
{public bool IsPalindrome(int x) {// 1: 特殊情况处理if (x < 0 || (x % 10 == 0 && x != 0)) {return false;}// 2: 变量声明与循环int revertedNumber = 0;while (x > revertedNumber) {// 3: 核心数学运算revertedNumber = revertedNumber * 10 + x % 10;x /= 10;}// 4: 最终的返回与比较return x == revertedNumber || x == revertedNumber / 10;}
}
练习题
选择题
1. 对于一个整数 int num = 12345;
, 以下哪个C#表达式可以得到数字 4
?
A. num / 100 % 10
B. num % 100 / 10
C. num / 10 % 100
D. (num / 10) % 10
简答题
2. 给你一个32位有符号整数 x
, 编写一个函数 Reverse(int x)
来反转 x
的每一位. 例如, 输入 123
, 输出 321
; 输入 -123
, 输出 -321
. 但你需要考虑一个重要问题: 反转后的数字可能会超出 int
的表示范围 (即溢出). 你将如何处理这个溢出问题? (提示: 不需要写完整的代码, 描述你的检查逻辑即可).
参考答案:
选择题答案
D.
num / 10
结果是1234
.1234 % 10
结果是4
.- 选项A:
12345 / 100
->123
.123 % 10
->3
. - 选项B:
12345 % 100
->45
.45 / 10
->4
. (这个也可以, 但D更符合逐位处理的常规思路). - 选项C:
12345 / 10
->1234
.1234 % 100
->34
.
(注: 选项B也能得到正确结果, 但D选项的操作 (num / 10) % 10
更能体现 “先移除个位, 再取新个位” 的逻辑, 具有更好的通用性. 两个都对, 但D的思路更具代表性.)
简答题答案
处理整数反转溢出问题的逻辑:
核心思想是在将新的数字添加到结果 result
之前, 检查这个操作是否会导致溢出.
假设我们正在构建反转后的数字 result
. 在每一步循环中, 我们会执行 result = result * 10 + digit
.
溢出可能发生在 result * 10
这一步.
-
检查正溢出: 在执行
result = result * 10 + digit
之前, 我们需要检查result * 10
是否会大于int.MaxValue
.- 一个安全的检查方法是:
if (result > int.MaxValue / 10) return 0;
(0通常作为错误/溢出标志). - 还需要考虑一个临界情况: 如果
result == int.MaxValue / 10
, 那么只有当digit
大于int.MaxValue % 10
(即7) 时才会溢出. 所以完整的检查是:if (result > int.MaxValue / 10 || (result == int.MaxValue / 10 && digit > 7)) return 0;
- 一个安全的检查方法是:
-
检查负溢出: 逻辑类似, 但使用
int.MinValue
.- 安全检查:
if (result < int.MinValue / 10 || (result == int.MinValue / 10 && digit < -8)) return 0;
(因为int.MinValue
是-2147483648
).
- 安全检查:
通过在每一步乘法和加法之前进行这样的 “预检查”, 我们可以有效地防止实际的溢出发生, 从而保证算法的正确性.
可能的实际用途:
- 底层数据处理: 在一些二进制数据处理, 或者需要对数字位进行操作的底层算法中, 这种直接操作数字位的技巧非常有用.
- 面试和技术评估: 这是最直接的应用. 这类问题是科技公司面试中评估候选人基本功和思维严谨性的经典题目. 掌握它能让我们在面试中表现得更出色.
第10题: 正则表达式匹配(难度: 困难)
原题:
给你一个字符串 s
和一个字符规律 p
,请你来实现一个支持 '.'
和 '*'
的正则表达式匹配。
'.'
匹配任意单个字符'*'
匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s
的,而不是部分字符串。
示例 1:
输入:s = "aa", p = "a"
输出:false
解释:"a" 无法匹配 "aa" 整个字符串。
示例 2:
输入:s = "aa", p = "a*"
输出:true
解释:因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
示例 3:
输入:s = "ab", p = ".*"
输出:true
解释:".*" 表示可匹配零个或多个('*')任意字符('.')。
提示:
1 <= s.length <= 20
1 <= p.length <= 20
s
只包含从a-z
的小写字母。p
只包含从a-z
的小写字母,以及字符.
和*
。- 保证每次出现字符
*
时,前面都匹配到有效的字符
开始解题:
此题需要用到动态规划(DP),技术能力暂时达不到,待我进修归来再进行讲解。