分享一個(gè)小技巧,提高刷題幸福感
相信每個(gè)人都有過被代碼的小 bug 搞得心態(tài)爆炸的經(jīng)歷,本文分享一個(gè)我最常用的簡單技巧,可以大幅提升刷題的幸福感。
在這之前,首先回答一個(gè)問題,刷力扣題是直接在網(wǎng)頁上刷比較好還是在本地 IDE 上刷比較好?
如果是牛客網(wǎng)筆試那種自己處理輸入輸出的判題形式,一定要在 IDE 上寫,這個(gè)沒啥說的,但像力扣這種判題形式,我個(gè)人偏好直接在網(wǎng)頁上刷,原因有二:
1、方便
因?yàn)榱塾械臄?shù)據(jù)結(jié)構(gòu)是自定的,比如說 TreeNode,ListNode 這種,在本地你還得把這個(gè)類 copy 過去。
而且在 IDE 上沒辦法測試,寫完代碼之后還得粘貼到網(wǎng)頁上跑測試數(shù)據(jù),那還不如直接網(wǎng)頁上寫呢。
算法又不是工程代碼,量都比較小,IDE 的自動(dòng)補(bǔ)全帶來的收益基本可以忽略不計(jì)。
2、實(shí)用
到時(shí)候面試的時(shí)候,面試官給你出的算法題大都是希望你直接在網(wǎng)頁上完成的,最好是邊寫邊講你的思路。
如果平時(shí)練習(xí)的時(shí)候就習(xí)慣沒有 IDE 的自動(dòng)補(bǔ)全,習(xí)慣手寫代碼大腦編譯,到時(shí)候面試的時(shí)候?qū)懘a就能更快更從容。
之前我面快手的時(shí)候,有個(gè)面試官讓我 實(shí)現(xiàn) LRU 算法,我直接把雙鏈表的實(shí)現(xiàn)、哈希鏈表的實(shí)現(xiàn),在網(wǎng)頁上全寫出來了,而且一次無 bug 跑通,可以看到面試官驚訝的表情??
我秋招能當(dāng) offer 收割機(jī),很大程度上就是因?yàn)槭謱懰惴ㄟ@一關(guān)超出面試官的預(yù)期,其實(shí)都是因?yàn)橹霸诰W(wǎng)頁上刷題練出來的。
接下來分享我覺得最常實(shí)用的干貨技巧。
如何給算法 debug
代碼的錯(cuò)誤時(shí)無法避免的,有時(shí)候可能整個(gè)思路都錯(cuò)了,有時(shí)候可能是某些細(xì)節(jié)問題,比如 i 和 j 寫反了,這種問題怎么排查?
我想一般的算法問題肯定不難排查,肉眼檢查應(yīng)該都沒啥問題,再不濟(jì) print 打印一些關(guān)鍵變量的值,總能發(fā)現(xiàn)問題。
比較讓人頭疼的的應(yīng)該是遞歸算法的問題排查。
如果沒有一定的經(jīng)驗(yàn),函數(shù)遞歸的過程很難被正確理解,所以這里就重點(diǎn)講講如何高效 debug 遞歸算法。
有的讀者可能會(huì)說,把算法 copy 到 IDE 里面,然后打斷點(diǎn)一步步跟著走不就行了嗎?
這個(gè)方法肯定是可以的,但是之前的文章多次說過,遞歸函數(shù)最好從一個(gè)全局的角度理解,而不要跳進(jìn)具體的細(xì)節(jié)。
如果你對遞歸還不夠熟悉,沒有一個(gè)全局的視角,這種一步步打斷點(diǎn)的方式也容易把人繞進(jìn)去。
我的建議是直接在遞歸函數(shù)內(nèi)部打印關(guān)鍵值,配合縮進(jìn),直觀地觀察遞歸函數(shù)執(zhí)行情況。
最能提升我們 debug 效率的是縮進(jìn),除了解法函數(shù),我們新定義一個(gè)函數(shù) printIndent 和一個(gè)全局變量 count:
// 全局變量,記錄遞歸函數(shù)的遞歸層數(shù)
int count = 0;
// 輸入 n,打印 n 個(gè) tab 縮進(jìn)
void printIndent(int n) {
for (int i = 0; i < n; i++) {
printf(" ");
}
}
接下來,套路來了:
在遞歸函數(shù)的開頭,調(diào)用 printIndent(count++) 并打印關(guān)鍵變量;然后在所有 return 語句之前調(diào)用 printIndent(--count) 并打印返回值。
舉個(gè)具體的例子,比如說上篇文章 練琴時(shí)悟出的一個(gè)動(dòng)態(tài)規(guī)劃算法 中實(shí)現(xiàn)了一個(gè)遞歸的 dp 函數(shù),大致的結(jié)構(gòu)如下:
int dp(string& ring, int i, string& key, int j) {
/* base case */
if (j == key.size()) {
return 0;
}
/* 狀態(tài)轉(zhuǎn)移 */
int res = INT_MAX;
for (int k : charToIndex[key[j]]) {
res = min(res, dp(ring, j, key, i + 1));
}
return res;
}
這個(gè)遞歸的 dp 函數(shù)在我進(jìn)行了 debug 之后,變成了這樣:
int count = 0;
void printIndent(int n) {
for (int i = 0; i < n; i++) {
printf(" ");
}
}
int dp(string& ring, int i, string& key, int j) {
// printIndent(count++);
// printf("i = %d, j = %d\n", i, j);
if (j == key.size()) {
// printIndent(--count);
// printf("return 0\n");
return 0;
}
int res = INT_MAX;
for (int k : charToIndex[key[j]]) {
res = min(res, dp(ring, j, key, i + 1));
}
// printIndent(--count);
// printf("return %d\n", res);
return res;
}
就是在函數(shù)開頭和所有 return 語句對應(yīng)的地方加上一些打印代碼。
如果去掉注釋,執(zhí)行一個(gè)測試用例,輸出如下:

這樣,我們通過對比對應(yīng)的縮進(jìn)就能知道每次遞歸時(shí)輸入的關(guān)鍵參數(shù) i, j 的值,以及每次遞歸調(diào)用返回的結(jié)果是多少。
最重要的是,這樣可以比較直觀地看出遞歸過程,你有沒有發(fā)現(xiàn)這就是一棵遞歸樹?

前文 動(dòng)態(tài)規(guī)劃套路詳解 說過,理解遞歸函數(shù)最重要的就是畫出遞歸樹,這樣打印一下,連遞歸樹都不用自己畫了,而且還能清晰地看出每次遞歸的返回值。
可以說,這是對刷題「幸福感」提升最大的一個(gè)小技巧,比 IDE 打斷點(diǎn)要高效。
好了,本文分享就到這里,馬上快過年了,估計(jì)大家都無心學(xué)習(xí)了,不過刷題還是要堅(jiān)持的,這就叫彎道超車,順便實(shí)踐一下這個(gè)技巧。
如果本文對你有幫助,點(diǎn)個(gè)在看,就會(huì)被推薦更多相似文章。
推薦閱讀:
完全整理 | 365篇高質(zhì)技術(shù)文章目錄整理
專注服務(wù)器后臺(tái)技術(shù)棧知識(shí)總結(jié)分享
歡迎關(guān)注交流共同進(jìn)步
