力扣刷题日常(7-8)

力扣刷题日常(7-8)

第7题: 整数反转(难度: 中等)

原题:

给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果.

如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0.

假设环境不允许存储 64 位整数(有符号或无符号).

示例 1:

输入:x = 123
输出:321

示例 2:

输入:x = -123
输出:-321

示例 3:

输入:x = 120
输出:21

示例 4:

输入:x = 0
输出:0

提示:

  • -231 <= x <= 231 - 1

开始解题:

首先,这道题虽然是中等难度,但它考察的重点——整数溢出处理,是编程中一个非常重要且实用的概念.

我们先来分析这道题的实现逻辑

算法实现逻辑

我们先从数学的角度思考,如何把一个数字 123 ,颠倒过来变成 321?

核心思想是数学方法,通过循环不断"剥离"原数字的末尾,然后"构建"新数字的开头

我们以 123 为栗子,来走一步这个流程

  1. 初始化: 我们需要一个变量来存储反转后的结果,命名为 reversed_x ,初始时 reversed_x = 0

  2. 第一轮循环:

    • 取末位: 如何得到最后一位呢----取余(%)运算. 123 % 10 的结果就是 3 .我们将这个数字定为 dight.
    • 构建新数: 把这个 dight 添加到 reversed_x 中. reversed_x = reversed_x * 10 + dight. 经过计算,现在 reversed_x 的值为 0 * 10 + 3 = 3
    • "砍掉"原数的末位: 如何砍掉原数的末位呢----整除(/)运算. 123 / 10 的结果就是 12 .现在 x 变成了 12.
  3. 第二轮循环:

    • 此时 x = 12,reversed_x = 3.

    • 取末位: 12 % 10 得到 2.(digit = 2)

    • 构建新数: reversed_x = 3 * 10 + 2,结果为 32.

    • “砍掉”原数的末位: 12 / 10 得到 1.现在 x 变成了 1.

  4. 第三轮循环:

    • 此时 x = 1,reversed_x = 32.

    • 取末位: 1 % 10 得到 1.(digit = 1)

    • 构建新数: reversed_x = 32 * 10 + 1,结果为 321.

    • “砍掉”原数的末位: 1 / 10 得到 0.现在 x 变成了 0.

  5. 循环结束: 我们的循环条件是 x != 0.现在 x 已经是 0 了,所以循环停止.最终的结果 reversed_x 就是 321.

如何处理特殊情况?
  • 负数 (如 -123): 这个数学方法同样适用
    • -123 % 10 结果是 -3.
    • -123 / 10 结果是 -12.
    • 整个过程下来,符号会被自然而然地保留,最终得到 -321.
  • 末尾是0 (如 120):
    • 第一轮,120 % 10 得到 0,reversed_x 变成 0.x 变成 12.
    • 后续步骤和 12 的反转一样,最终得到 21.前导零被自动消除了.
本题最大的难点: 处理整数溢出

题目要求环境是32位有符号整数,范围是 [-2^31, 2^31 - 1],也就是 [-2147483648, 2147483647].

在我们的核心步骤 reversed_x = reversed_x * 10 + digit 中,reversed_x * 10 的结果可能会超出这个范围.由于题目不允许使用64位整数(long)来临时存储,我们不能等溢出发生了再去判断.我们必须在它发生之前就预判到.

如何进行预判:

我们来分析一下正数溢出的情况.溢出即将发生在 reversed_x 乘以 10 的过程中

int 的最大值为 int.MaxValue (即 2147483647)

  1. 一个粗略的检查: 如果 reversed_x 大于 int.MaxValue / 10 (即 214748364),那么它再乘以10,无论 digit 是多少,都必然会溢出.
  2. 一个精确的检查: 如果 reversed_x 正好等于 int.MaxValue / 10 (即 214748364),那么能否溢出就取决于 digit 了.
    • int.MaxValue 的末尾是 7.
    • 如果 digit 大于 7,那么 214748364 * 10 + digit 就会大于 2147483647,导致溢出.
    • 如果 digit 小于等于 7,就不会溢出.

所以,正数溢出的完整判断条件是:

if (reversed_x > int.MaxValue / 10 || (reversed_x == int.MaxValue / 10 && digit > 7))

同理,负数溢出的判断条件(int.MinValue-2147483648):
if (reversed_x < int.MinValue / 10 || (reversed_x == int.MinValue / 10 && digit < -8))

一旦检测到即将溢出,我们就按题目要求返回 0.

简单总结:
  1. 初始化 reversed_x = 0.
  2. 使用 while 循环,条件是 x != 0.
  3. 在循环内部:
    a. 通过 x % 10 取出 x 的末位数字 digit.
    b. 在执行乘法前,进行溢出检查.如果检查到即将溢出,立即 return 0.
    c. 如果安全,则更新 reversed_x = reversed_x * 10 + digit.
    d. 通过 x / 10 “砍掉” x 的末位.
  4. 循环结束后,返回 reversed_x.

算法代码:

public class Solution 
{public int Reverse(int x) {// 用于存储反转后的结果int reversed = 0;// 循环直到x的所有位都被处理while (x != 0) {// 1. 取出x的末尾数字// C#的取余(%)运算会自动处理符号,例如 -123 % 10 的结果是 -3int digit = x % 10;// 2. “砍掉”x的末尾数字// C#的整除(/)运算同样会自动处理符号,例如 -123 / 10 的结果是 -12x /= 10;// 3. 【核心】在构建新数之前,检查是否会溢出// 检查正数溢出:// a) reversed > 214748364 (int.MaxValue / 10)//    如果成立,那么 reversed * 10 必然大于 int.MaxValue,溢出.// b) reversed == 214748364 && digit > 7//    如果成立,那么 reversed * 10 + digit 必然大于 2147483647,溢出.if (reversed > int.MaxValue / 10 || (reversed == int.MaxValue / 10 && digit > 7)) {return 0; // 发生正向溢出,返回0}// 检查负数溢出:// a) reversed < -214748364 (int.MinValue / 10)//    如果成立,那么 reversed * 10 必然小于 int.MinValue,溢出.// b) reversed == -214748364 && digit < -8//    如果成立,那么 reversed * 10 + digit 必然小于 -2147483648,溢出.if (reversed < int.MinValue / 10 || (reversed == int.MinValue / 10 && digit < -8)) {return 0; // 发生负向溢出,返回0}// 4. 安全,构建新数// 将取出的末位数字拼接到reversed的末尾reversed = reversed * 10 + digit;}// 循环结束,返回最终结果return reversed;}
}

知识点总结:

  1. 数值溢出:一个“沉默的杀手”

    • 核心思想:任何有固定大小的数据类型(如 byte, short, int, long)都有其表示范围.当计算结果超出这个范围时,就会发生“溢出”.在C#中,整数溢出默认是不抛出异常的,它会“环绕”(wrap around),例如 int.MaxValue + 1 会变成 int.MinValue.这种不报错的错误行为非常隐蔽,是很多逻辑bug的根源.
    • 通用原则:在进行任何可能导致结果急剧增大的运算(尤其是乘法和累加)时,都要有意识地思考:“我的结果会超出数据类型的边界吗?”
  2. 防御性编程:在错误发生前预判

    • 核心思想:不要等到溢出发生后再去处理(因为你可能根本察觉不到),而是在执行运算之前,就检查这次运算是否安全.
    • 本题范例:if (reversed > int.MaxValue / 10 ...) 就是一个完美的防御性编程范例.我们通过逆向思维(从 int.MaxValue 反推),为即将执行的 reversed * 10 操作建立了一个“安全护栏”.
    • 通用原则:对于关键运算,先检查其操作数是否在安全范围内,再执行运算.这比“事后补救”要健壮得多.
  3. 数学方法处理数字:高效且独立

    • 核心思想:使用取余 (%) 和整除 (/) 是一套“组合拳”,可以逐位拆解任何整数,而无需将其转换为字符串.
    • 优点:这种纯数学处理通常比字符串转换和操作(如 ToString(), ToCharArray(), int.Parse())的性能要高得多,并且不产生额外的内存分配(GC),这在对性能敏感的Unity Update 循环中尤其重要.
    • 通用原则:当需要处理数字的各个位时,优先考虑 %/ 的数学方法.

练习题:

选择题

1. 在本题的溢出判断 if (reversed > int.MaxValue / 10 || ...) 中,为什么我们用 int.MaxValue / 10 进行比较,而不是直接写 if (reversed * 10 > int.MaxValue)

A. 因为 reversed * 10 可能会先于比较操作发生溢出,导致 if 判断的条件本身就是基于一个错误(已环绕)的值,从而判断失效.
B. 这是一种性能优化,除法比乘法快.
C. 因为 int.MaxValue 不能被10整除,所以必须先做除法.
D. 为了让代码更易读.

2. 如果你需要获取一个正整数 n (例如 n = 54321) 的百位数(也就是 3),以下哪个表达式是正确的?

A. n % 100
B. n / 100
C. (n / 100) % 10
D. n % 1000 / 100


简答题

3. 假设你需要反转一个 short 类型的整数(范围: -32768 到 32767).请参照本题的思路,写出针对 short 类型的正数溢出检查的 if 条件语句.


参考答案:
选择题答案

1. 在本题的溢出判断 if (reversed > int.MaxValue / 10 || ...) 中,为什么我们用 int.MaxValue / 10 进行比较,而不是直接写 if (reversed * 10 > int.MaxValue)

正确答案:A

解析:

  • A. 因为 reversed * 10 可能会先于比较操作发生溢出,导致 if 判断的条件本身就是基于一个错误(已环绕)的值,从而判断失效.
    • 这是最关键的原因.在C#中,if (A > B) 这个表达式会先计算 A 的值,再计算 B 的值,最后进行比较.如果 reversed 的值已经很大(例如 300000000),那么在执行 reversed * 10 时,结果就已经超出了 int 的最大值,发生了溢出并“环绕”成了一个负数.此时,if 语句就变成了 if (一个负数 > int.MaxValue),这个条件永远是 false,导致我们错过了真正的溢出,程序会继续使用这个错误的负数进行计算.而 reversed > int.MaxValue / 10 这种写法,将运算转移到了不易溢出的一侧,从而安全地预判了风险.
  • B. 这是一种性能优化,除法比乘法快.
    • 这个说法是错误的.在大多数现代处理器上,整数乘法通常比整数除法要快.
  • C. 因为 int.MaxValue 不能被10整除,所以必须先做除法.
    • int.MaxValue 能否被10整除与判断逻辑的正确性无关.
  • D. 为了让代码更易读.
    • 虽然代码可读性很重要,但在这里,最主要的原因是为了保证逻辑的正确性,防止因运算顺序导致溢出,从而使判断失效.

2. 如果你需要获取一个正整数 n (例如 n = 54321) 的百位数(也就是 3),以下哪个表达式是正确的?

正确答案:C (选项 D 在数学上也是正确的,但 C 是更通用和标准的方法)

解析:

  • 我们的目标是隔离出百位上的 3.
  • 第一步:去掉百位右边的所有数字. 我们可以通过整除 100 来实现.
    54321 / 100 的结果是 543.现在,我们想要的目标数字 3 成为了结果的个位数.
  • 第二步:从新结果中取出个位数. 我们可以通过对 10 取余来实现.
    543 % 10 的结果是 3.
  • 所以,组合起来就是 (n / 100) % 10.
  • 为什么 D n % 1000 / 100 也可以?
    • 54321 % 1000 的结果是 321(取出了后三位).
    • 321 / 100 的结果是 3(对后三位数取百位).
    • 虽然结果正确,但选项 C 的思路(“移位”再“取末位”)更具有普适性.例如,要取任意第 k 位的数字,通用的公式就是 (n / 10^k) % 10.
简答题答案:
   // short.MaxValue 是 32767// short.MaxValue / 10 是 3276// short.MaxValue % 10 是 7// 假设 digit 是当前取出的末位数,rev 是 short 类型的反转结果if (rev > short.MaxValue / 10 || (rev == short.MaxValue / 10 && digit > 7)){// 即将发生正向溢出return 0; }

实际应用场景:

  1. 游戏经济系统(金币、资源)

    • 场景:玩家的货币数量通常用 intlong 存储.如果一个玩家拥有接近 int.MaxValue 的金币,此时他完成一个任务获得了少量金币,若不检查溢出,他的金币数可能会瞬间从一个巨大的正数变成负数,导致经济系统崩溃.
    • 应用:在增加玩家货币的函数中,必须加入溢出检查.例如 if (playerGold > int.MaxValue - rewardAmount),如果成立,则直接将玩家金币设为 int.MaxValue,而不是执行加法.
  2. 计分与伤害计算

    • 场景:在一个伤害可以无限叠加的“割草”游戏中,或者一个分数可以滚得极高的街机游戏中,总伤害或总分数很容易超过 int 的上限.
    • 应用:使用 long 类型来存储分数/伤害值.或者,在每次累加伤害前,进行防御性检查,防止数值环绕.
  3. UI显示和格式化

    • 场景:你需要将一个分数 12345 显示成带有闪烁效果的单个数字 1, 2, 3, 4, 5.
    • 应用:可以使用 %/ 的技巧,循环取出每个数字,然后为每个数字实例化一个UI组件并应用动画,而无需进行字符串操作.
  4. 程序化生成(Procedural Generation)

    • 场景:使用一个整数种子(Seed)来生成关卡.你可以通过拆解这个种子的各位数字来决定不同的生成规则.
    • 应用:例如,seed % 10 的结果决定地图主题(0=森林, 1=沙漠…),(seed / 10) % 10 的结果决定敌人密度,以此类推.这让一个简单的整数种子可以控制多个生成维度.

第8题: 字符串转换整数 (atoi) (难度: 中等)

原题(这部分粘贴存在问题,可自行去力扣查看):

请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数.

函数 myAtoi(string s) 的算法如下:

  1. 空格: 读入字符串并丢弃无用的前导空格(" "
  2. 符号: 检查下一个字符(假设还未到字符末尾)为 '-' 还是 '+'.如果两者都不存在,则假定结果为正.
  3. 转换: 通过跳过前置零来读取该整数,直到遇到非数字字符或到达字符串的结尾.如果没有读取数字,则结果为0.
  4. 舍入: 如果整数数超过 32 位有符号整数范围 [−231, 231 − 1] ,需要截断这个整数,使其保持在这个范围内.具体来说,小于 −231 的整数应该被舍入为 −231 ,大于 231 − 1 的整数应该被舍入为 231 − 1 .

返回整数作为最终结果.

示例 1:

输入: s = “42”

输出: 42

解释: 加粗的字符串为已经读入的字符,插入符号是当前读取的字符.

带下划线线的字符是所读的内容,插入符号是当前读入位置.
第 1 步:"42"(当前没有读入字符,因为没有前导空格)^
第 2 步:"42"(当前没有读入字符,因为这里不存在 '-' 或者 '+')^
第 3 步:"42"(读入 "42")^

示例 2:

输入: s = " -042"

输出:-42

解释:

第 1 步:"   -042"(读入前导空格,但忽视掉)^
第 2 步:"   -042"(读入 '-' 字符,所以结果应该是负数)^
第 3 步:"   -042"(读入 "042",在结果中忽略前导零)^

示例 3:

输入: s = “1337c0d3”

输出: 1337

解释:

第 1 步:"1337c0d3"(当前没有读入字符,因为没有前导空格)^
第 2 步:"1337c0d3"(当前没有读入字符,因为这里不存在 '-' 或者 '+')^
第 3 步:"1337c0d3"(读入 "1337";由于下一个字符不是一个数字,所以读入停止)^

示例 4:

输入: s = “0-1”

输出: 0

解释:

第 1 步:"0-1" (当前没有读入字符,因为没有前导空格)^
第 2 步:"0-1" (当前没有读入字符,因为这里不存在 '-' 或者 '+')^
第 3 步:"0-1" (读入 "0";由于下一个字符不是一个数字,所以读入停止)^

示例 5:

输入: s = “words and 987”

输出: 0

解释:

读取在第一个非数字字符“w”处停止.

提示:

  • 0 <= s.length <= 200
  • s 由英文字母(大写和小写)、数字(0-9)、' ''+''-''.' 组成

开始解题:

那么这道题的本质是模拟一个字符串解析器. 我们可以把它想象成一个机器人,它会从左到右扫描一个字符串,并根据一套严格的规定来决定自己该做什么.如果我们将这个过程分解,就会发现它非常符合逻辑,就像在Unity中编写一个角色的AI状态机一样.

我们可以把整个过程分为几个连续的状态或步骤:

第一步: 初始状态 -> "寻找数字"状态

目标: 跳过所有无关紧要的前导空格.

  • 机器人行为: 我们的机器人从字符串的第一个字符(索引 0)开始.它会问自己:“这个字符是空格吗?”
    • 如果是,它会简单的向前走一步(索引 +1),然后继续问同样的问题.
    • 如果不是,或者已经走到了字符串的尽头,这个阶段就结束了.
  • 关键点: 这个阶段是"清理"阶段,为后续真正的解析做准备.如果整个字符串都是空格,那么机器人走到头也找不到任何有用的东西,最终就应该返回 0.
第二步: "寻找数字"状态 -> "确认符号"状态

目标: 在找到第一个非空格字符后,判断数字的正负.

  • 机器人行为: 机器人停在了第一个非空格字符上.它会检查这个字符:

    • '-' 吗?如果是,它就在自己的小本本上记下“这是个负数”,然后向前走一步,准备读取数字.
    • '+' 吗?如果是,它记下“这是个正数”(或者什么都不记,因为默认就是正数),然后也向前走一步.
    • 如果既不是 '-' 也不是 '+',机器人就认为这是一个正数,并且停在原地不动,因为这个字符可能就是数字本身.
  • 关键点: 符号只在数字的最前面出现才有效.例如,对于 " -42",这个阶段会记录负号;但对于 " 4-2",这个阶段会认为数字是正数,因为第一个非空格字符是 '4'.

第三步: "确认符号"状态 -> "转换数字"状态

目标: 连续读取所有数字字符,并将它们组合成一个整数.

  • 机器人行为: 现在机器人已经准备好读取核心数字了.它会继续向前扫描:

    • “当前字符是数字('0''9')吗?”
    • 如果是,它就执行一个核心操作:当前结果 = 当前结果 * 10 + 这个数字的值.
      • 例如,如果当前结果是 4,读到了新数字 '2',那么新结果就是 4 * 10 + 2 = 42.
      • 如果当前结果是 42,读到了新数字 '3',那么新结果就是 42 * 10 + 3 = 423.
    • 如果当前字符不是数字,或者走到了字符串的尽头,这个阶段就立刻结束.
  • 一个巨大的陷阱(核心难点):整数溢出(第7题)

    • C# 的 int 类型有范围限制,即 [-2147483648, 2147483647].
    • 在执行 当前结果 = 当前结果 * 10 + 这个数字的值 之前,我们必须预判这次计算是否会导致结果超出范围.
    • 如何预判?
      • 假设我们正在构建一个正数.int 的最大值是 2147483647.
      • 在乘以10之前,如果 当前结果 已经大于 int.MaxValue / 10 (即 214748364),那么再乘以10必然溢出.
      • 或者,如果 当前结果 正好等于 int.MaxValue / 10 (即 214748364),那么我们就要看下一个数字了.如果这个数字大于 int.MaxValue % 10 (即 7),那么加法操作就会导致溢出.
    • 如果预判到会溢出,就不能再继续计算了,必须立即停止并返回该方向的极值(正数返回 int.MaxValue,负数返回 int.MinValue).
第四步: "转换数字"状态 -> "最终返回"状态

目标: 结合符号和计算出的数值,得到最终结果.

  • 机器人行为: 数字读取循环结束后,我们得到了一个数值(在计算过程中我们一直把它当作正数处理).

    • 现在,机器人拿出它的小本本,看看在第二步记录的符号是什么.
    • 如果是“负数”,就把计算出的数值乘以 -1.
    • 如果是“正数”,就保持原样.
    • 最后,返回这个最终的整数.
  • 特殊情况: 如果从头到尾都没有读到任何一个数字(例如 "words and 987" 或者 " -abc"),那么“转换数字”这个阶段根本就不会执行,我们计算出的数值会保持初始值 0.最终返回 0,完全符合题意.

总结一下逻辑:

开始 -> 循环跳过空格 -> 判断并记录符号 -> 循环读取数字并处理溢出 -> 结合符号返回结果


代码与讲解:

public class Solution {public int MyAtoi(string s) {// 1. 初始化变量int i = 0; // 我们的“机器人”或指针,从字符串开头出发int sign = 1; // 符号,默认为正long result = 0; // 使用 long 类型来存储中间结果,方便检测溢出int n = s.Length;// 2. 步骤一:丢弃前导空格while (i < n && s[i] == ' ') {i++;}// 3. 步骤二:检查符号if (i < n && (s[i] == '+' || s[i] == '-')) {sign = (s[i] == '-') ? -1 : 1;i++;}// 4. 步骤三:转换数字并处理溢出while (i < n && char.IsDigit(s[i])) {// 将字符转换为数字int digit = s[i] - '0';// 核心:构建数字result = result * 10 + digit;// 核心:在构建过程中检查是否溢出if (sign * result > int.MaxValue) {return int.MaxValue;}if (sign * result < int.MinValue) {return int.MinValue;}i++;}// 5. 步骤四:结合符号并返回最终结果// 将 long 类型的结果乘以符号,并强制转换为 intreturn (int)(sign * result);}
}
代码讲解:
  1. long 数据类型

    • 是什么: long 是一个64位有符号整数类型,它的范围比32位的 int 大得多(大约是 -9 x 10^18 到 9 x 10^18).
    • 为什么用在这里: 这是处理整数溢出问题的一个绝佳技巧.题目要求我们检测结果是否会超出 int 的范围.如果我们直接用 int 来累加,一旦溢出,它就会变成一个意想不到的负数(或正数),我们就丢失了溢出前的信息.通过使用 long 这个“更大的容器”来存放计算结果,我们可以安全地进行 result = result * 10 + digit 操作.然后,在每一步都检查这个 long 类型的结果是否已经超出了 int 的范围.这让溢出判断变得非常简单和清晰.
  2. *int.MaxValueint.MinValue

    • 是什么: 它们是 System.Int32 结构体中定义的两个公共静态常量(public static const).int.MaxValue 的值是 2,147,483,647,int.MinValue 的值是 -2,147,483,648.
    • 如何使用: 它们为我们提供了 int 类型的精确边界,是进行范围判断和“截断”(Clamping)操作的权威标准.在代码中,我们用 if (sign * result > int.MaxValue) 来判断是否超过了正数边界.这比自己手写 2147483647 要更具可读性,也更不容易出错.在Unity中,我们常用 Mathf.Infinity,这与 int.MaxValue 在概念上是类似的,都代表了某种类型的边界.
  3. char.IsDigit(char c)

    • 是什么: 这是一个非常方便的静态方法,用于判断一个给定的字符是否是十进制数字(‘0’ 到 ‘9’).
    • 为什么用它: 在C#中,我们当然可以写 s[i] >= '0' && s[i] <= '9'.但这有几个小缺点:可读性稍差,且它隐含了字符编码是连续的假设(虽然对于数字来说总是如此).使用 char.IsDigit() 是更推荐的“C#风格”写法,它意图明确,代码更干净.
  4. 字符的算术运算:s[i] - '0'

    • 是什么: 这是C#(以及C/C++/Java)中一个非常经典和高效的技巧.在内部,char 类型实际上是存储为一个数字(其Unicode编码值).幸运的是,所有数字字符 '0', '1', '2', ... '9' 的编码是连续的.
    • 如何工作: 因此,用一个数字字符的编码值减去 '0' 的编码值,得到的结果恰好就是这个数字的整数值.例如,'5' - '0' 的结果就是整数 5.这比 int.Parse(s[i].ToString()) 这种先转成字符串再解析的方式要快得多,是性能敏感场景下的首选.
  5. 三元运算符:condition ? value_if_true : value_if_false

    • 是什么: 这是一个紧凑的 if-else 语句的简写形式.

    • 在代码中的应用: sign = (s[i] == '-') ? -1 : 1; 这行代码等价于:

      if (s[i] == '-') {sign = -1;
      } else {sign = 1;
      }
      

      对于这种简单的赋值逻辑,三元运算符能让代码更简洁.


知识点总结:

1. 边界条件与防御性编程
  • 核心思想: 永远不要相信输入.一个健壮的程序必须能处理各种预料之外的、不规范的、甚至是恶意的输入.
  • 在本题中的体现:
    • 空字符串或纯空格: s = ""s = " ".
    • 非数字开头: s = "words and 987".
    • 符号位置不正确: s = " + 42" vs s = "42+".
    • 溢出: s = "2147483648" (比 int.MaxValue 大1).
    • 混合输入: s = " -042a123".
  • 通用启示: 在编写任何函数或方法时,都要先思考:“最坏的输入情况是什么?”.在函数开头处理这些边缘情况(如检查 null 或空集合),可以让我们的主逻辑更清晰、更安全.
2. 使用“更大”的数据类型处理溢出
  • 核心思想: 当一个计算过程有可能超出某个数值类型的范围时,使用一个范围更大的类型作为“临时计算器”是一种简单而有效的策略.
  • 在本题中的体现: 我们使用 long (64位) 来计算一个最终要存入 int (32位) 的结果.这使得我们可以在不丢失信息的情况下,轻松地将中间结果与 int.MaxValueint.MinValue 进行比较.
  • 通用启示: 这个技巧不限于 intlong.例如,在处理需要高精度的小数运算时,我们可能会选择使用 decimal 类型而不是 floatdouble 来避免精度损失.在处理图形学或物理计算时,有时为了中间步骤的精度,会使用 double 进行计算,最后再将结果转回 float.
3. 状态机思想 (State Machine)
  • 核心思想: 将一个复杂的过程分解为一系列离散的、定义清晰的“状态”,以及在这些状态之间转换的“规则”.
  • 在本题中的体现: 我们的解析过程可以看作一个简单的状态机:
    1. State_Skipping_Whitespace (跳过空格状态)
    2. State_Determining_Sign (确定符号状态)
    3. State_Reading_Digits (读取数字状态)
    4. State_Finished (完成状态)
      程序根据当前字符,从一个状态转换到下一个状态.
  • 通用启示: 状态机是解决许多问题的强大模型,尤其是在游戏开发中.角色的AI(站立、行走、攻击、防御)、UI的交互流程(主菜单、设置、游戏中)、网络协议的握手过程等,都可以用状态机来清晰地建模和实现.

练习题:

选择题

1. 在一个需要返回 int 的函数中,你正在累加一个可能很大的数字.为了防止计算过程中发生溢出,以下哪个是最佳实践?

A. 在每次加法后,检查结果是否变成了负数,如果是,说明溢出了.
B. 使用 try-catch 块包裹加法运算,捕捉 OverflowException 异常.
C. 将累加器变量声明为 long 类型,每次累加后,检查该 long 值是否超出了 int 的范围.
D. 在进行加法前,使用 int.MaxValue - currentValue < numberToAdd 的方式进行预判.

简答题

2. 假设你需要编写一个函数 ParseVector2(string s),它能将形如 "(1.5, -2.0)" 的字符串解析为一个 Unity 的 Vector2 对象.请简要描述你会如何运用“状态机”的思想来设计这个函数的解析逻辑?(不需要写代码,描述步骤即可)


参考答案
  1. 答案:C 或 D 都是优秀的实践,但 C 更为通用和简单.
    解析:

    • A 是不可靠的.对于正数溢出,结果会变成负数,但对于负数下溢,结果可能变成正数,逻辑复杂且容易出错.
    • B 在默认的 C# 编译设置下是行不通的.整数溢出默认不会抛出异常(为了性能).你需要使用 checked 关键字才能让它抛出异常,这在性能敏感的循环中可能不是最佳选择.
    • C (本题解法) 是最直观和简单的方法.它将问题从“如何检测溢出”转变为“如何比较大小”,逻辑清晰.
    • D (预判法) 也是一种非常好的、不依赖更大类型的方法,性能很高.它直接在操作前判断本次操作是否会导致溢出.在某些不能使用更大类型的场景下,这是标准做法.
  2. 答案:
    我们可以将解析过程设计为以下几个状态:

    1. 寻找左括号状态: 从头开始,跳过所有空格,直到找到第一个 '('.如果没找到,则格式错误.
    2. 解析X值状态:'(' 之后开始,解析一个浮点数(这本身就可以复用 atoi 的逻辑,但要处理小数点).直到遇到 ',' 字符.将解析出的值存为 X.
    3. 寻找逗号状态: 在解析完 X 后,跳过可能的空格,寻找 ','.如果没找到,则格式错误.
    4. 解析Y值状态:',' 之后开始,用与解析 X 值相同的方法解析第二个浮点数.直到遇到 ')' 字符.将解析出的值存为 Y.
    5. 寻找右括号状态: 在解析完 Y 后,跳过可能的空格,寻找 ')'.如果没找到,则格式错误.
    6. 完成状态: 成功找到所有部分,用解析出的 X 和 Y 构建并返回 new Vector2(x, y).

    在任何一步失败(比如找不到预期的字符,或数字格式错误),函数都应立即停止并返回一个错误或默认值.


可能的实际应用:

  1. 配置文件解析: 游戏中的配置文件(.ini, .txt, .json, .xml)经常包含需要被读取为数值的设置,如音量大小、图形质量等级、玩家初始生命值等.一个健壮的解析器是读取这些配置的基础.

  2. 自定义编辑器工具: 在Unity Editor中,我们可能会创建一些工具窗口,让策划或美术在输入框里填写数值(例如,批量修改一堆怪物的血量).我们需要将输入框中的字符串安全地转换为整数或浮点数,atoi 的逻辑是这个过程的核心.

  3. 网络通信: 从服务器接收的数据包可能是以文本协议(如HTTP)传输的.解析协议头或消息体中的数字字段时,就需要用到类似 atoi 的健壮的字符串到数字的转换逻辑.

  4. 调试控制台/GM命令: 游戏内通常会有一个调试控制台,允许开发者输入命令,如 set_player_hp 1000.解析这个命令中的参数 1000,就需要一个能处理各种输入的 atoi 函数.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/93547.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/93547.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

串口接收数据包(协议带帧头帧尾)的编程实现方法:1、数据包格式定义结构体2、使用队列进行数据接收、校验解包

这种带帧头帧尾的数据包处理流程可以简单概括为 “识别边界→提取有效数据→验证完整性” 三个核心步骤&#xff0c;具体操作如下&#xff1a;1. 数据包格式定义&#xff08;先约定规则&#xff09;首先明确一个 “合格数据包” 的结构&#xff0c;比如&#xff1a; 帧头&#…

JSON 对象封装教程

JSON 对象封装方法在 Java 中封装 JSON 对象通常使用第三方库&#xff0c;如 org.json、Gson 或 Jackson。以下是几种常见的方法&#xff1a;使用 org.json 库添加 Maven 依赖&#xff1a;<dependency><groupId>org.json</groupId><artifactId>json<…

【WRF-Chem】EDGAR 排放数据处理:分部门合并转化为二进制(Python全代码)

目录 process.py process_biofl.py process_fossil.py process_micro.py process_sector.py 参考 process.py 读取 EDGAR 排放数据库中 2000 至 2023 年间不同行业的甲烷(CH₄)排放数据,进行合并处理,并将总排放以二进制格式保存到文件中。 导入必要的库 import numpy as n…

【学习过程记录】【czsc】1、安装

文章目录 背景 安装 安装python 安装czsc 功能测试 附录 奇葩的报错 背景 详见: https://github.com/waditu/czsc 安装 安装python !重要!作者强调,python必须是大于等于3.8 为此呢,我也是花了一点时间装了一个python3.13。 安装czsc 关于czsc的安装呢,官方也是给出…

Python批量生成N天前的多word个文件,并根据excel统计数据,修改word模板,合并多个word文件

1&#xff0c;需求 根据word模板文件&#xff0c;生成多个带日期后缀的word文件根据excel-每日告警统计数量&#xff0c;逐个修改当日的文档2&#xff0c;实现 shell脚本&#xff1a;根据word模板文件&#xff0c;生成多个带日期后缀的word文件 #!/bin/bash # 生成近一年日期 …

基于uni-app的血糖血压刻度滑动控件

想要做一个基于uni-app的血糖血压刻度滑动控件&#xff0c;hbuilder市场没有好的&#xff0c;参照别人的写了一个。如图&#xff1a;源码&#xff0c;自己放入components里面。<!-- 刻度滑动选择 --> <template><view><view class"slide-title"…

C语言(02)——标准库函数大全(持续更新)

想要了解更多的C语言知识&#xff0c;可以订阅下面的专栏&#xff0c;里面也有很多品质好文&#xff1a; 打怪升级之路——C语言之路_ankleless的博客-CSDN博客 还在持续更新中&#xff0c;以下是学习过程中遇到的一些库函数&#xff08;排序不分先后&#xff09;&#xff1a…

永磁同步电机无速度算法--静态补偿电压模型Harnefors观测器

一、原理介绍本文基于Harnefors教授提出的静态补偿电压模型&#xff0c;可以实现带载零速启动、正反转切换等功能&#xff0c;原理清晰&#xff0c;实现简便。二、仿真模型在MATLAB/simulink里面验证所提算法&#xff0c;搭建仿真。采用和实验中一致的控制周期1e-4&#xff0c;…

[SKE]Python gmssl库的C绑定

Python gmssl库的C绑定 摘要:本文展示gmssl库的C绑定,并给出完整代码。将参考模型从Python脚本迁移到纯C代码中使用gmssl库(TongSuo项目,支持国密算法如SM4,同时兼容AES、DES、3DES、RSA等)。这样,UVM(SystemVerilog)可以通过DPI-C直接调用C函数,而无需嵌入Py…

4.方法的使用

方法是指一段具有独立功能的代码块&#xff0c;只有被调用时才会执行方法的主要作用体现在&#xff1a;代码组织&#xff1a;将原本挤在一起的臃肿代码按照功能进行分类管理例如&#xff1a;将用户注册的验证逻辑、数据库操作、结果返回等分离成不同方法提高复用性&#xff1a;…

day21-Excel文件解析

目录 1. 概述 2. Apache POI 3. XSSF解析Excel文件 3.1. 添加Jar包依赖 3.2. Workbook&#xff08;Excel文件&#xff09; 3.2.2. 加载&#xff08;解析&#xff09;Excel文件 3.3. Sheet &#xff08;工作簿&#xff09; 3.3.1. 创建工作簿 3.3.2. 获取工作簿 3.3.3.…

与 TRON (波场) 区块链进行交互的命令行工具 (CLI): tstroncli

源码仓库 一个基于 Node.js 和 TypeScript 构建的&#xff0c;用于与 TRON (波场) 区块链进行交互的命令行工具 (CLI)。 本项目旨在提供一个简单、可扩展的框架&#xff0c;让开发者可以轻松地通过命令行调用 TRON 的 HTTP API&#xff0c;实现查询链上信息、发送交易等操作。…

rabbitmq--默认模式(点对点)

导入包&#xff1a;<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId> </dependency>application.yml springrabbitmq:host: localhostport: 5672username: guestpassword: gue…

外网访问文档编辑器Docsify(Windows版本),内网穿透技术应用简便方法

如果你正在为项目写文档&#xff0c;但又不想折腾复杂的构建流程&#xff0c;也不想维护一堆静态 HTML 文件&#xff0c;那你一定要试试 docsify。docsify 是一个基于 JavaScript 的开源文档生成工具&#xff0c;它最大的特点就是“无构建”&#xff1a;你只需要写 Markdown 文…

第4章唯一ID生成器——4.5 美团点评开源方案Leaf

Leaf是美团点评公司基础研发平台推出的一个唯一ID生成器服务&#xff0c;其具备高可靠性、低延迟、全局唯一等特点&#xff0c;目前已经被广泛应用于美团金融、美团外卖、美团酒旅等多个部门。Leaf根据不同业务的需求分别实现了Leaf-segment和Leaf-snowflake两种方案&#xff0…

分布式搜索和分析引擎Elasticsearch实战指南

ES 介绍与安装 Elasticsearch&#xff0c; 简称 ES&#xff0c;它是个开源分布式搜索引擎&#xff0c;它的特点有&#xff1a;分布式&#xff0c;零配置&#xff0c;自动发现&#xff0c;索引自动分片&#xff0c;索引副本机制&#xff0c;restful 风格接口&#xff0c;多数据源…

【13】C# 窗体应用WinForm——.NET Framework、WinForm、工程创建、工具箱简介、窗体属性及创建

文章目录1. WinForm工程创建 及 界面介绍1.1 WinForm工程创建1.2 窗体 Form1.cs “查看代码”1.3 打开窗体设计器2. 工具箱3. 窗体属性及创建3.1 窗体属性3.2 实例&#xff1a;创建一个新窗体3.2.1 添加新Windows窗体3.2.2 窗体属性配置3.2.3 设置该窗体为启动窗体WinForm 是 W…

论文阅读-IGEV

文章目录1 概述2 模块2.1 总体说明2.2 特征抽取器2.3 CGEV2.4 基于Conv-GRU的更新算子2.5 空间上采样2.6 损失函数3 效果参考文献1 概述 在双目深度估计中&#xff0c;有一类是基于3D卷积的方法&#xff0c;代表就是PSMNet&#xff0c;它应用 3D 卷积编码器-解码器来聚合和正则…

[2025CVPR-图象分类方向]SPARC:用于视觉语言模型中零样本多标签识别的分数提示和自适应融合

1. ​背景与问题定义​ 视觉语言模型&#xff08;如CLIP&#xff09;在单标签识别中表现出色&#xff0c;但在零样本多标签识别&#xff08;MLR&#xff09;任务中表现不佳。MLR要求模型识别图像中多个对象&#xff08;例如&#xff0c;图像包含“猫”和“沙发”&#xff09;&…

2025创始人IP如何破局?

内容持续更新却无人点赞&#xff0c;课程精心打磨却无人报名&#xff0c;直播卖力讲解却无人停留 —— 明明有内容、有经验、有成果&#xff0c;却始终难以打动用户。问题的核心&#xff0c;或许在于你尚未打造出真正的 “创始人IP”。‌一、创始人IP&#xff1a;不止标签&…