力扣刷題日常(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
英文地址,請注明出處:http://en.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;不止標簽&…