典題解析:水手分葉子的倒推法與遞推實(shí)現(xiàn))
記得我在某高校C語言程序設(shè)計(jì)課上第一次看到“水手分葉子”這道題時(shí)第一反應(yīng)是這不就是個(gè)循環(huán)模擬嗎后來才發(fā)現(xiàn)這道題如果真用循環(huán)模擬去寫雖然也能出答案但背后的數(shù)學(xué)味道全丟了。它表面上是一道C語言作業(yè)題實(shí)際上考的是“倒推法”、整除性質(zhì)、遞推關(guān)系以及怎么用代碼把數(shù)學(xué)規(guī)律翻譯成可執(zhí)行的邏輯。如果你正在啃C語言循環(huán)、函數(shù)或者準(zhǔn)備機(jī)試/面試我很建議花點(diǎn)時(shí)間把這個(gè)題吃透。它能讓你理解一個(gè)很重要的編程思維很多看似要暴力枚舉的問題換一個(gè)角度看可以讓代碼量減半、速度提升幾個(gè)數(shù)量級(jí)。下面我把這道題的完整拆解、數(shù)學(xué)推導(dǎo)、C語言實(shí)現(xiàn)以及各種坑都寫出來希望能幫你徹底弄懂。1. 題目還原與實(shí)際應(yīng)用場(chǎng)景1.1 一道C語言課上的“葉子題”題目大概長(zhǎng)這樣五個(gè)水手在沙灘上發(fā)現(xiàn)一堆葉子。夜里第一個(gè)水手醒過來把這堆葉子分成五堆發(fā)現(xiàn)多出一片于是把多出的一片丟進(jìn)海里然后拿走其中一堆。第二個(gè)水手醒來也這樣做把剩下的葉子分成五堆又發(fā)現(xiàn)多出一片丟掉拿走一堆。第三、第四、第五個(gè)水手都重復(fù)了同樣的操作。第二天早晨他們五人把最后剩下的葉子平均分成五堆剛剛好不再有多余。問最初這堆葉子至少有多少片“葉子”只是一個(gè)敘事外殼換成椰子、蘋果、金幣數(shù)學(xué)結(jié)構(gòu)完全一樣。老師之所以用“葉子”大概率是為了避免“椰子”這種南方意象給學(xué)生造成跳躍感也可能是這道題從某個(gè)小學(xué)奧數(shù)題改編過來時(shí)順手換的詞。不管怎樣它本質(zhì)上是經(jīng)典“水手分椰子”問題的變身。這道題作為C語言程序設(shè)計(jì)作業(yè)難度中等偏上。如果你只是照著題目做正向模擬代碼可以寫完但容易出現(xiàn)兩個(gè)問題一是不知道初始值枚舉到什么時(shí)候是個(gè)頭二是沒有抓住“倒推”這個(gè)關(guān)鍵導(dǎo)致代碼復(fù)雜且難以推廣。而如果你先在紙上推一遍數(shù)學(xué)關(guān)系再敲代碼整個(gè)過程會(huì)非常清爽。1.2 把故事變成遞推公式我們?cè)O(shè)初始葉子總數(shù)為 x0。每次“水手操作”可以用一個(gè)函數(shù)來描述把一堆數(shù)量為 x 的葉子分成五堆因?yàn)槎嘁黄瑏G掉一片再拿走五分之一也就是拿走一堆最后剩下的數(shù)量為x (x - 1) × 4 / 5這里有兩個(gè)隱含條件第一操作前 x 必須滿足 x mod 5 1第二操作后 x 必須是整數(shù)。五位水手依次操作得到x0 → x1 → x2 → x3 → x4 → x5第二天早晨x5 被平均分成五堆所以 x5 必須能被 5 整除。設(shè)早晨每一堆有 y 片葉子則 x5 5y。已知 y我們可以倒推因?yàn)?x5 (x4 - 1) × 4 / 5所以 x4 x5 × 5 / 4 1同理x3 x4 × 5 / 4 1一直倒推到 x0倒推的前提是每一步都是整數(shù)也就是 x5、x4、x3、x2、x1 都必須能被 4 整除。為什么是 4因?yàn)槲逦凰掷锬米咭欢阎笫O碌氖撬亩?。推廣到 n 個(gè)水手倒推時(shí)就要看當(dāng)前剩余量能不能被 n-1 整除倒推公式為pre current / (n - 1) × n 1整個(gè)題目的數(shù)學(xué)模型就是這樣一個(gè)遞推關(guān)系。理解了這個(gè)代碼就只是把這些公式翻譯成 C 語言而已。1.3 換個(gè)物品不影響數(shù)學(xué)模型你可能覺得“葉子那么輕水手為什么要分”這種問題很搞笑但上手寫代碼時(shí)反而容易受影響總想著葉子片數(shù)是不是有什么特殊物理意義。其實(shí)在編程題里物品數(shù)量只是一個(gè)整數(shù)變量。換成“海盜分金幣”“猴子分桃”“工人分零件”只要你把“每次多一個(gè)拿走一份”的規(guī)則抽象出來代碼一字不改就能用。所以遇到這類題第一步永遠(yuǎn)是抽象出數(shù)學(xué)模型而不是盯著題面里的“葉子”兩個(gè)字。這也是我想強(qiáng)調(diào)的C語言不僅僅是語法更是一種表達(dá)算法思想的工具。你能不能用代碼描述一個(gè)遞推過程比你能不能背出 switch 語法更重要。2. 兩種核心思路正向枚舉與逆向構(gòu)造2.1 正向模擬的思路與代價(jià)最直觀的思路是枚舉初始葉子數(shù) x從 1 開始逐個(gè)嘗試。每次循環(huán)模擬五位水手的操作for (long long x 1; ; x) { long long temp x; int ok 1; for (int i 0; i 5; i) { if (temp % 5 ! 1) { ok 0; break; } temp (temp - 1) * 4 / 5; } if (ok temp % 5 0) { printf(%lld\n, x); break; } }邏輯上完全沒問題。經(jīng)典的5人分椰子問題最小解是3121這個(gè)循環(huán)枚舉到3121才找到答案中間大約要執(zhí)行一萬多次模除和乘法運(yùn)算對(duì)計(jì)算機(jī)來說小菜一碟。所以我并不是說正向模擬不能做而是想說這種解法沒有利用題目本身的結(jié)構(gòu)導(dǎo)致三個(gè)隱患。第一個(gè)隱患是不知道上界。你只能用一個(gè) while(1) 無限試下去心里沒底。如果你把水手人數(shù)從 5 改成 8答案會(huì)變成幾十萬甚至幾百萬級(jí)別循環(huán)次數(shù)會(huì)呈指數(shù)級(jí)上漲效率就很難看了。第二個(gè)隱患是容易把浮點(diǎn)運(yùn)算攪進(jìn)來。有人為了避免整數(shù)除法的截?cái)鄬懗?double結(jié)果在數(shù)字變大時(shí)出現(xiàn)精度誤差最后答案差幾個(gè)數(shù)排查半天。第三個(gè)隱患是代碼不優(yōu)雅面試官追問“為什么答案是3121”時(shí)你只能說“枚舉出來的”無法展示數(shù)學(xué)思維。2.2 倒推法為什么又快又優(yōu)雅倒推法的思路是不猜初始值改猜“最后早晨每一堆的葉子片數(shù) y”。y 從 1 開始枚舉一旦確定了 y就可以唯一確定整個(gè)倒推鏈最后剩余量 5y第5次操作前 5y × 5 / 4 1第4次操作前 第5次操作前 × 5 / 4 1……初始值 第一次操作前倒推過程中只要某一步不能被 4 整除說明當(dāng)前 y 不合法直接讓 y 繼續(xù)試。第一個(gè)能讓五步倒推全部整除的 y對(duì)應(yīng)的初始值就是最小解。由于倒推的每一步都嚴(yán)格增大所以 y 越小初始值越小因此按 y 遞增枚舉找到的第一個(gè)就是正確答案。這個(gè)方法的巧妙之處在于它把“初始值未知”變成了“最終每份數(shù)量已知”而后者是一個(gè)我們從 1 開始枚舉的小數(shù)字。雖然倒推也需要枚舉但枚舉的是 y而不是巨大的初始值。更重要的是每一步都用整數(shù)運(yùn)算和取余判斷不可能產(chǎn)生浮點(diǎn)誤差。代碼也更短核心就是一個(gè)循環(huán)加一個(gè)取余條件。2.3 實(shí)際做題怎么選在作業(yè)里兩種方法都能拿分。但如果老師要求分析算法、說明思路倒推法明顯更占優(yōu)勢(shì)。我自己的建議是先花五分鐘在紙上推公式再寫代碼。哪怕你一開始只想正向模擬也最好順便用倒推法再實(shí)現(xiàn)一遍因?yàn)楹芏鄼C(jī)試、面試會(huì)追問“還能不能優(yōu)化”“復(fù)雜度如何”。你只要說出“倒推法從最后一份開始每次檢查整除性”對(duì)方就知道你理解了問題的本質(zhì)。而且倒推法還有一個(gè)好處它非常容易參數(shù)化。把水手人數(shù)從 5 改成任意 n只需要把公式中的 5 替換成 n、4 替換成 n-1代碼幾乎不用動(dòng)。這為你以后解決“n個(gè)水手分葉子”的變體鋪平了道路。3. C語言實(shí)現(xiàn)從函數(shù)設(shè)計(jì)到完整代碼3.1 數(shù)據(jù)類型選擇long long 是底線寫 C 語言第一個(gè)大坑是數(shù)據(jù)類型。很多人看到答案是 3121覺得 int 綽綽有余。確實(shí)只算 5 個(gè)水手的話 int 沒問題。但你要是想寫一個(gè)通用函數(shù)跑 n8、n10 的情況答案可能輕松超過 21 億int 就會(huì)溢出變成負(fù)數(shù)while 循環(huán)再也找不到答案甚至死循環(huán)。所以我的習(xí)慣是涉及這種可能迭代增長(zhǎng)的整數(shù)問題一律用 long long。倒推過程中current 每次大約變?yōu)樵瓉淼?5/4 倍連續(xù)倒推 5 次初始值至少是最后剩余量的幾倍到幾十倍。當(dāng) n 變大時(shí)增長(zhǎng)速度會(huì)更快。用 long long 雖然不能保證絕對(duì)不溢出但對(duì)于課程設(shè)計(jì)級(jí)別的問題已經(jīng)足夠了。如果你想讓代碼更嚴(yán)謹(jǐn)一點(diǎn)還可以在函數(shù)入口處加一個(gè)參數(shù)范圍檢查比如 n 小于等于 20然后說明超出范圍可能溢出。不過大部分題目用不到這種防御。重點(diǎn)記住乘法之前最好顯式轉(zhuǎn)換為 long long比如 (long long)n * lastShare避免 n 和 lastShare 還是 int 時(shí)發(fā)生 int 乘法溢出。3.2 設(shè)計(jì)一個(gè)參數(shù)化的核心函數(shù)我們希望函數(shù)不只解決 5 個(gè)人的情況而是能計(jì)算 n 個(gè)水手、每次多一片、最后早晨正好分完的最小初始葉子數(shù)。函數(shù)簽名可以這樣寫long long findMinimumLeaves(int n) { long long lastShare 1; // 最后早晨每一堆的葉子數(shù) while (1) { long long current (long long)n * lastShare; // 最后早晨的剩余總量 int ok 1; // 倒推 n 次對(duì)應(yīng) n 個(gè)水手夜里的操作 for (int step 0; step n; step) { if (current % (n - 1) ! 0) { ok 0; break; } current current / (n - 1) * n 1; } if (ok) { return current; } lastShare; } }解釋一下代碼邏輯lastShare是最后早晨分完后每一堆的數(shù)量也是每個(gè)人早上拿走的數(shù)量。current初始為n * lastShare即最后一次夜里操作后剩余的葉子總數(shù)。倒推n次每次檢查current能否被n-1整除能整除說明上一個(gè)狀態(tài)是整數(shù)然后用公式還原出上一個(gè)狀態(tài)。如果某一步不能整除說明當(dāng)前l(fā)astShare不滿足條件外層循環(huán)讓lastShare加 1重新嘗試。注意倒推次數(shù)是n而不是n1。因?yàn)轭}目中每一位水手都在夜里執(zhí)行了一次“多一片、拿走一堆、留下 n-1 堆”的操作而早晨只是把剩下的正好分完這個(gè)早晨動(dòng)作沒有“留下一堆”的過程所以不需要納入倒推循環(huán)只需要在初始current里體現(xiàn)“能被 n 整除”這個(gè)條件即可。3.3 main 函數(shù)與完整代碼下面是一段可以直接編譯運(yùn)行的完整代碼#include stdio.h long long findMinimumLeaves(int n) { long long lastShare 1; while (1) { long long current (long long)n * lastShare; int ok 1; for (int step 0; step n; step) { if (current % (n - 1) ! 0) { ok 0; break; } current current / (n - 1) * n 1; } if (ok) { return current; } lastShare; } } int main() { int n 5; long long answer findMinimumLeaves(n); printf(%d個(gè)水手分葉子最初最少需要 %lld 片。\n, n, answer); return 0; }運(yùn)行結(jié)果應(yīng)當(dāng)輸出5個(gè)水手分葉子最初最少需要 3121 片。這里printf的%lld必須和long long匹配。如果寫成%d在大多數(shù)平臺(tái)上會(huì)輸出錯(cuò)誤值甚至導(dǎo)致未定義行為。這是一個(gè)非常常見的錯(cuò)誤我在下面“常見錯(cuò)誤”部分還會(huì)重點(diǎn)提。3.4 手動(dòng)驗(yàn)證 3121 為什么成立代碼拿到答案后建議自己手動(dòng)驗(yàn)算一遍這樣你對(duì)遞推的理解會(huì)深很多。我們?cè)O(shè)初始值 x0 3121第一個(gè)水手操作前3121 5 × 624 1。丟掉1片拿走1堆624片剩下 624 × 4 2496 片。第二個(gè)水手操作前2496 5 × 499 1。丟掉1片拿走1堆499片剩下 499 × 4 1996 片。第三個(gè)水手操作前1996 5 × 399 1。丟掉1片拿走1堆399片剩下 399 × 4 1596 片。第四個(gè)水手操作前1596 5 × 319 1。丟掉1片拿走1堆319片剩下 319 × 4 1276 片。第五個(gè)水手操作前1276 5 × 255 1。丟掉1片拿走1堆255片剩下 255 × 4 1020 片。第二天早晨1020 5 × 204正好分成五堆每堆 204 片。完美符合要求。用倒推法最后早晨每堆數(shù)量 y 204最后剩余量 5y 1020。倒推一次1020 / 4 × 5 1 1276再倒推一次1276 / 4 × 5 1 1596接著得到 1996、2496、3121。整個(gè)鏈條嚴(yán)絲合縫。這個(gè)驗(yàn)證過程非常值得你在草稿紙上走一遍。它不但能檢驗(yàn)代碼還能幫你建立“正向操作”和“逆向還原”之間的對(duì)應(yīng)關(guān)系。以后你再遇到類似題腦子里會(huì)自然有一條時(shí)間線x0 → x1 → … → x5正向是“減一、乘五分之四”逆向是“整除四、乘五再加一”。4. 常見錯(cuò)誤與調(diào)試實(shí)錄4.1 把“整除4”誤寫成“整除5”這是我見過最多的問題。很多同學(xué)搞不清倒推時(shí)到底檢查什么順手寫成if (current % n ! 0)或者if (current % 5 ! 0)結(jié)果程序一個(gè)解都找不到或者找到錯(cuò)誤解。正推和倒推的判斷條件不一樣正向模擬時(shí)每次操作前要求x % n 1也就是數(shù)量除以水手?jǐn)?shù)余 1。倒推時(shí)檢查的是當(dāng)前剩余量能否被n-1整除。為什么倒推要看n-1因?yàn)閺纳弦粻顟B(tài)還原到當(dāng)前狀態(tài)需要把當(dāng)前剩余量乘以 n 再除以 n-1。要保證結(jié)果是整數(shù)只需當(dāng)前剩余量能被 n-1 整除。這個(gè)條件跟n本身沒有直接關(guān)系只跟“拿走一份之后剩下幾份”有關(guān)。記住一個(gè)口訣正向看余數(shù)逆向看整除正向看 n逆向看 n-1。4.2 忘記倒推公式里的“1”倒推公式是pre current / (n - 1) * n 1很多同學(xué)在草稿上推導(dǎo)時(shí)記得有個(gè)加 1敲代碼時(shí)一順手就丟了寫成current / (n - 1) * n。這樣算出來的初始值會(huì)比真實(shí)值小一點(diǎn)比如 5 人時(shí)可能會(huì)得到 3120。為什么差 1因?yàn)檎虿僮骼飦G掉了 1 片葉子倒推時(shí)必須把這 1 片加回來。丟掉的不是 0而是 1。我建議在代碼里把加 1 單獨(dú)寫一行并加上注釋比如// 還原操作前的數(shù)量當(dāng)前剩余是 (pre - 1) 的 (n-1)/n因此 pre current/(n-1)*n 1 current current / (n - 1) * n 1;這樣的注釋在復(fù)習(xí)時(shí)能省很多腦細(xì)胞。4.3 枚舉起點(diǎn)選錯(cuò)導(dǎo)致死循環(huán)我見過有人把lastShare的初始值寫成 0。表面上看0 也是一個(gè)正整數(shù)最后早晨每堆 0 片意味著沒有葉子那倒推出來的初始值會(huì)是什么呢按公式current n * 0 0然后 0 % (n-1) 0倒推 pre 0/(n-1)*n1 1。連續(xù)倒推 n 次每次都從 1 開始得到初始值 1。但 1 片葉子根本不能滿足第一次操作“分成 n 堆余 1”的條件1 小于 n所以這是一個(gè)偽解。因此lastShare必須從 1 開始。這也是一個(gè)很容易被忽略的邊界問題。另外外層枚舉的lastShare千萬不要忘記在函數(shù)末尾遞增。如果你在成功分支 return那么遞增語句只有失敗分支會(huì)執(zhí)行。要注意循環(huán)變量的作用域別把lastShare寫在 if 外面卻因?yàn)?continue 跳過了導(dǎo)致死循環(huán)。4.4 用 printf 追蹤中間量快速定位問題當(dāng)你發(fā)現(xiàn)代碼跑不出正確結(jié)果時(shí)別急著瞎改直接在循環(huán)里加兩行打印printf(lastShare%lld step%d current%lld\n, lastShare, step, current);然后運(yùn)行一次觀察倒推過程。比如 n5 時(shí)你會(huì)看到lastShare203 step0 current1015然后程序停在current % 4 ! 0。這說明 203 這個(gè) y 不合法于是繼續(xù)試 204才進(jìn)入下一步。這種調(diào)試方法雖然原始但非常有效。它能把抽象的遞推過程可視化幫你確認(rèn)每一步取余是否如期成立。還有一個(gè)很隱蔽的坑printf格式符。long long必須用%lld如果你寫%d有些編譯器會(huì)警告有些則直接靜默輸出錯(cuò)誤。有時(shí)候你查了半天邏輯最后發(fā)現(xiàn)只是打印格式錯(cuò)了那種滋味比寫錯(cuò)算法還難受。5. 擴(kuò)展同一道題能玩出多少花樣5.1 變體A第二天早晨最后一次分葉子也“多一片”題目最常見的變體是第二天早晨五位水手最后一次把剩余葉子分成五堆時(shí)也發(fā)現(xiàn)多了一片又丟進(jìn)海里然后每人拿走一堆最后什么都不剩。這種情況下早晨分葉子不再是“正好分完”而是同樣執(zhí)行了一次“減一、分成 n 份、拿走 n 份”的動(dòng)作。怎么改代碼關(guān)鍵在于最后剩余量不再是n * lastShare而是n * lastShare 1多出來的1片被丟掉。這里的lastShare表示早晨每人最終拿走的那一堆的葉子數(shù)。早晨操作前剩余葉子總數(shù)等于n * lastShare 1。然后仍然倒推 n 次夜里 n 位水手的操作。把這個(gè)邏輯封裝成帶withMorningExtra參數(shù)的函數(shù)long long solveVariant(int n, int withMorningExtra) { long long lastShare 1; while (1) { long long current; if (withMorningExtra) { current (long long)n * lastShare 1; } else { current (long long)n * lastShare; } int ok 1; for (int step 0; step n; step) { if (current % (n - 1) ! 0) { ok 0; break; } current current / (n - 1) * n 1; } if (ok) return current; lastShare; } }以 n2 為例你可以手動(dòng)驗(yàn)證變體A的最小初始值是 15。模擬一下初始 15 片第一個(gè)水手分成 2 堆每堆 7多 1 片丟掉 1拿走一堆 7剩下 7。第二個(gè)水手把 7 分成 2 堆每堆 3多 1 片丟掉 1拿走一堆 3剩下 3。早晨把 3 分成 2 堆每堆 1多 1 片丟掉 1兩人各拿一堆正好拿完。如果你在紙上驗(yàn)算會(huì)發(fā)現(xiàn)“丟 1 片”的動(dòng)作一共發(fā)生了 3 次兩個(gè)夜里加一個(gè)早晨所以答案從標(biāo)準(zhǔn)題的 5 變成了 15。這個(gè)對(duì)比非常直觀也能幫助你理解題目條件對(duì)結(jié)果的影響。5.2 變體B每次丟掉的不是1片而是m片如果題目改成“每次分葉子時(shí)多出 m 片全部丟掉然后拿走一堆”那么遞推公式里的“1”應(yīng)該改成“m”。正向操作變?yōu)閤 (x - m) × (n - 1) / n倒推公式變?yōu)閜re current / (n - 1) × n m整除條件依然是current % (n - 1) 0。因?yàn)閜re要整數(shù)只需要current / (n - 1) × n m是整數(shù)而 m 本身是整數(shù)所以核心條件不變。你可以把 m 加入到函數(shù)參數(shù)里做一個(gè)更通用的solve(n, m, withMorningExtra)。這個(gè)小擴(kuò)展能讓你看到題目的變化只是把常數(shù) 1 換成了常量 m代碼結(jié)構(gòu)沒有任何改變。這就是數(shù)學(xué)建模的好處。5.3 變體C一共操作 k 次而不是 n 次有時(shí)候題目會(huì)說“只有前 3 個(gè)水手夜里醒來各操作一次后面兩個(gè)水手睡到天亮最后早晨正好分完”。這種情況下倒推次數(shù)就不是 n 而是 k。實(shí)現(xiàn)上只改一個(gè)循環(huán)次數(shù)即可。甚至可以把operations作為參數(shù)傳入long long solveFlexible(int n, int operations, int discarded) { long long lastShare 1; while (1) { long long current (long long)n * lastShare; int ok 1; for (int i 0; i operations; i) { if (current % (n - 1) ! 0) { ok 0; break; } current current / (n - 1) * n discarded; } if (ok) return current; lastShare; } }參數(shù)多了以后要特別注意邊界條件如果operations為 0循環(huán)不執(zhí)行直接返回current n * lastShare最小值是 n。這雖然數(shù)學(xué)上成立但已經(jīng)不是原題的敘事場(chǎng)景了。所以在測(cè)試時(shí)要給操作次數(shù)設(shè)置合理的范圍。5.4 同余方程視角從程序設(shè)計(jì)到初等數(shù)論如果你還想再深挖一層可以看看這個(gè)問題背后的同余方程結(jié)構(gòu)。設(shè)初始量為 x每次操作后剩余量滿足x1 (x - 1) × (n-1)/n要讓 x1 是整數(shù)需要 x ≡ 1 (mod n) 且 (x-1) 能被 n 整除。倒推思路等價(jià)于解一個(gè)線性同余方程組。最終可以化簡(jiǎn)出 x 關(guān)于 y 的一個(gè)顯式表達(dá)式。對(duì)于 n5可以推導(dǎo)出x 4 × 5^5 × y / 4^5 ... 之類帶有分?jǐn)?shù)冪的式子。不過對(duì)C語言課程而言用枚舉倒推已經(jīng)足夠。了解一點(diǎn)同余思想只是為了讓你在遇到更大規(guī)模問題時(shí)能意識(shí)到這個(gè)問題和“解同余方程”有關(guān)也許可以用數(shù)學(xué)方法直接算出公式而不是依賴循環(huán)。如果你以后學(xué)習(xí)算法競(jìng)賽這類題目還會(huì)經(jīng)常以“中國(guó)剩余定理”的變體形式出現(xiàn)。我個(gè)人在實(shí)際操作中的體會(huì)是與其背下這道題的答案不如徹底理解“從最后反推”的思維模式。很多遞推類題目比如“猴子偷桃”“約瑟夫環(huán)”“漢諾塔”都藏著類似的逆向思路。你在這道題上花的時(shí)間會(huì)在很多意想不到的地方回本。最后再分享一個(gè)小技巧寫代碼之前先用手算一個(gè)最小 n 的例子比如 n2把答案算出來再寫函數(shù)測(cè)試你的代碼。如果 n2 的結(jié)果對(duì)再去跑 n5通常不會(huì)出問題。這個(gè)小習(xí)慣能幫你隔離“邏輯錯(cuò)誤”和“實(shí)現(xiàn)錯(cuò)誤”省下大量調(diào)試時(shí)間。