計(jì)基礎(chǔ)》OJ期末考2.23復(fù)盤:題型拆解與避坑指南)
1. 考試整體情況與備考定位1.1 2.23這場考試的獨(dú)特之處第一次看到“程序設(shè)計(jì)基礎(chǔ) 2022級(jí) OJ期末考試 2.23”這個(gè)標(biāo)題的吉林大學(xué)同學(xué)多半是在課程群里或者學(xué)長學(xué)姐的復(fù)習(xí)資料里翻到的。這里先解釋一下2.23不是日期而是這場OJ期末考試的場次編號(hào)或者題單編號(hào)具體指2022級(jí)《程序設(shè)計(jì)基礎(chǔ)》課程的期末上機(jī)考試批次。這類考試和傳統(tǒng)紙質(zhì)試卷完全不同所有題目都在在線評(píng)測系統(tǒng)OJOnline Judge上完成提交代碼后由系統(tǒng)自動(dòng)判定正確與否沒有人工閱卷環(huán)節(jié)也沒有“過程分”可拿。這場考試的核心特征是題目量不算夸張但時(shí)間壓力大、邊界條件多、對(duì)基礎(chǔ)功底的考察非常細(xì)。和平時(shí)在PTA、洛谷、Codeforces上刷題不同課程OJ考試的題目風(fēng)格更貼近教學(xué)大綱幾乎不會(huì)出現(xiàn)高級(jí)算法考的就是順序結(jié)構(gòu)、分支判斷、循環(huán)嵌套、數(shù)組處理、字符串操作、結(jié)構(gòu)體排序、基礎(chǔ)遞歸這幾類。換句話說這門課考的不是“你會(huì)不會(huì)算法”而是“你能不能把課上學(xué)過的東西在限定時(shí)間內(nèi)準(zhǔn)確無誤地寫出來”。我之所以專門寫這篇復(fù)盤是因?yàn)閹н^好幾屆學(xué)弟學(xué)妹復(fù)習(xí)這類考試發(fā)現(xiàn)大家的問題驚人地一致平時(shí)作業(yè)能過一到考試就卡在輸入格式、邊界條件、變量類型這些小坑上最后成績和平時(shí)水平完全不匹配。這篇文章會(huì)從考試題型拆解、經(jīng)典題目分析、踩坑實(shí)錄、答題策略四個(gè)角度展開把這場考試?yán)镒钪档糜涀〉臇|西全部講透。1.2 這類考試適合誰參考如果你正在準(zhǔn)備吉林大學(xué)《程序設(shè)計(jì)基礎(chǔ)》的OJ期末考試這篇文章就是給你寫的。哪怕你不是吉大的只要你的學(xué)校程序設(shè)計(jì)基礎(chǔ)課也采用OJ考試模式里面的題型分析、踩坑記錄和答題策略同樣可以直接套用。另外如果你是大一剛學(xué)完C語言、正打算期末沖刺的新生這篇文章能幫你快速定位復(fù)習(xí)方向少走彎路。需要提前說明的是這篇文章不會(huì)貼出某道題目的完整標(biāo)準(zhǔn)答案代碼——一是因?yàn)椴煌攴蓊}目有差異直接抄代碼沒有意義二是因?yàn)镺J考試真正的難點(diǎn)從來不是“代碼怎么寫”而是“為什么這樣寫才對(duì)”后者才是這篇文章想重點(diǎn)解決的。2. 題型結(jié)構(gòu)與核心考點(diǎn)拆解2.1 七道題的整體布局以2022級(jí)這場2.23場次為例期末考試一共7道題總用時(shí)約3小時(shí)具體時(shí)長以當(dāng)年通知為準(zhǔn)一般在2.5到3小時(shí)之間。題目難度分布大致呈階梯狀前兩道屬于熱身題級(jí)別只要會(huì)基本輸入輸出就能得分中間三道的核心考點(diǎn)在循環(huán)邊界、數(shù)組下標(biāo)、字符串處理最后兩道拉開差距一道是結(jié)構(gòu)體排序或鏈表操作一道是遞歸或簡單回溯。為什么說這個(gè)布局很有代表性因?yàn)樗珳?zhǔn)地反映了《程序設(shè)計(jì)基礎(chǔ)》這門課的教學(xué)目標(biāo)第一確保你掌握了最基本的程序框架頭文件、主函數(shù)、輸入輸出格式第二考察你能否把分支和循環(huán)用對(duì)、用活第三檢驗(yàn)?zāi)銓?duì)數(shù)組和字符串這些連續(xù)內(nèi)存結(jié)構(gòu)的敏感度第四確認(rèn)你具備用結(jié)構(gòu)體組織數(shù)據(jù)、用排序解決問題的初級(jí)工程能力第五看看你是否理解遞歸的“遞”和“歸”這兩個(gè)過程。這種階梯式分布也意味著答題順序非常重要。我見過太多同學(xué)一上來死磕最后一道遞歸題結(jié)果卡了四十分鐘前面能穩(wěn)拿的分也沒時(shí)間寫了。正確的策略永遠(yuǎn)是先把能拿的分全部裝進(jìn)口袋再回頭處理難題。2.2 每類題的命題思路與得分要點(diǎn)第一類基礎(chǔ)輸入輸出題。這類題往往描述很長但核心要求就一句話——按指定格式讀入若干個(gè)數(shù)經(jīng)過簡單計(jì)算后按格式輸出。得分要點(diǎn)在于嚴(yán)格匹配輸出樣例的空格、換行、大小寫。很多同學(xué)在這類題上丟分不是因?yàn)椴粫?huì)算而是多打了一個(gè)空格、少寫了一個(gè)換行。第二類分支判斷與數(shù)學(xué)計(jì)算題。典型場景是分段函數(shù)、成績等級(jí)、日期計(jì)算判斷閏年、求某年某月有多少天。這類題的坑集中在邊界值上比如“大于等于”和“大于”的區(qū)別再比如閏年判斷中“能被4整除但不能被100整除或能被400整除”這個(gè)條件少寫一個(gè)括號(hào)就是錯(cuò)。第三類循環(huán)與嵌套循環(huán)題。常見考法是打印各種圖案菱形、三角形、數(shù)字方陣、求水仙花數(shù)、求素?cái)?shù)、迭代求值。得分要點(diǎn)是注意循環(huán)變量的初值和結(jié)束條件防止死循環(huán)和越界訪問。這類題在OJ上最常見的錯(cuò)誤是“Time Limit Exceeded”原因多半是循環(huán)變量寫錯(cuò)導(dǎo)致死循環(huán)。第四類數(shù)組與字符串題。典型場景有數(shù)組逆序、數(shù)組元素去重、求最大最小值和下標(biāo)、字符串統(tǒng)計(jì)、字符串替換、回文判斷。這類題需要特別注意數(shù)組下標(biāo)從0開始以及字符串末尾的\0。在處理字符數(shù)組時(shí)gets和scanf(%s)混用會(huì)導(dǎo)致緩沖區(qū)殘留換行符這是經(jīng)典到不能再經(jīng)典的坑。第五類結(jié)構(gòu)體與排序題。典型場景是學(xué)生成績統(tǒng)計(jì)、成績排名、按照某種規(guī)則排序后輸出。得分要點(diǎn)是熟練掌握qsort或者自己寫冒泡/選擇排序以及理解結(jié)構(gòu)體比較函數(shù)的寫法。這類題往往攜帶“自定義規(guī)則排序”的考察點(diǎn)讀題時(shí)必須把排序規(guī)則中的每一個(gè)字都看清楚比如“總分相同時(shí)按學(xué)號(hào)從小到大”。第六類遞歸與遞推題。常見有漢諾塔、斐波那契數(shù)列、逆序輸出、簡單分治。這類題代碼量往往不大但思考難度較高。得分要點(diǎn)是明確遞歸終止條件和遞歸轉(zhuǎn)移公式并在草稿紙上手動(dòng)演算小規(guī)模數(shù)據(jù)確認(rèn)邏輯正確。2.3 一個(gè)容易被忽略的隱藏考點(diǎn)代碼風(fēng)格OJ自動(dòng)判題系統(tǒng)不在乎你的代碼風(fēng)格好不好看但部分課程考試的OJ系統(tǒng)會(huì)設(shè)置“代碼查重”和“編譯警告檢測”。前者提醒你千萬別抄別人的代碼后者意味著如果你的代碼編譯時(shí)產(chǎn)生warning有些嚴(yán)格配置的系統(tǒng)會(huì)直接判定編譯錯(cuò)誤。尤其是main函數(shù)的返回值類型、未使用的變量、類型轉(zhuǎn)換不匹配這類問題GCC編譯器的warning信息很明確但很多初學(xué)者根本不看編譯提示等到OJ給我報(bào)“Compile Error”的時(shí)候才慌神。我的建議是考試前自己確認(rèn)一下環(huán)境用的哪個(gè)編譯器版本一般是GCC系列了解常見的編譯警告提示答題時(shí)不寫會(huì)產(chǎn)生warning的代碼從源頭上避免這個(gè)隱藏扣分項(xiàng)。3. 經(jīng)典題目復(fù)盤與解題思路詳解3.1 熱身題里的陷阱輸出格式即正義先說第一道題題目大概是“輸入兩個(gè)整數(shù)a和b輸出它們的和”看起來是不是簡單到令人發(fā)指但這道題的滿分率并不是100%原因就在輸出格式上。有的同學(xué)會(huì)寫成#include stdio.h int main() { int a, b; scanf(%d%d, a, b); printf(%d\n, a b); return 0; }這道題正常情況下沒問題但如果題目要求輸出“Sum 結(jié)果”這種帶提示詞的格式而你只輸出一個(gè)數(shù)字那OJ就會(huì)給你一個(gè)無情的“Wrong Answer”。所以我的習(xí)慣是拿到題先看輸出樣例而不是先看輸入描述。輸出樣例長什么樣我的代碼就嚴(yán)格輸出什么樣一個(gè)空格不多一個(gè)空格不少。這類題還有一個(gè)進(jìn)階版本輸入多組數(shù)據(jù)每組占一行直到文件結(jié)束EOF。這時(shí)候就必須用while (scanf(%d%d, a, b) ! EOF)或者while (~scanf(%d%d, a, b))。有同學(xué)不知道這個(gè)寫法只處理了第一組數(shù)據(jù)后面的全沒讀到導(dǎo)致只過了一個(gè)測試點(diǎn)。3.2 必考的日期類問題閏年判斷的完整寫法第二道高頻題是日期相關(guān)比如“輸入一個(gè)年份和月份輸出該月有多少天”。這道題考察的是分支嵌套和閏年規(guī)則。完整靠譜的寫法是#include stdio.h int isLeap(int year) { return (year % 4 0 year % 100 ! 0) || (year % 400 0); } int main() { int year, month; int days[12] {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; scanf(%d%d, year, month); if (month 2 isLeap(year)) { printf(29\n); } else { printf(%d\n, days[month - 1]); } return 0; }注意這里days[month - 1]的下標(biāo)如果用戶輸入的month是1到12那下標(biāo)范圍就是0到11剛好對(duì)應(yīng)。很多同學(xué)在考試時(shí)會(huì)忘記月份和下標(biāo)之間的偏移關(guān)系直接寫days[month]從而讀到了數(shù)組越界的內(nèi)存垃圾值輸出就變成了一堆莫名其妙的數(shù)字。另外一個(gè)易錯(cuò)點(diǎn)是把閏年判斷條件寫成year % 4 0。這個(gè)條件能判斷大多數(shù)閏年但遇到1900年就會(huì)出錯(cuò)——1900年能被4整除但不是閏年。OJ測試數(shù)據(jù)里一定會(huì)包含這種邊界年份就是為了抓你條件寫得不夠嚴(yán)謹(jǐn)。3.3 圖案打印題先畫圖再寫循環(huán)再來說說循環(huán)嵌套的經(jīng)典題型——打印菱形/三角形。這類題看起來復(fù)雜其實(shí)核心就是找到行號(hào)與空格數(shù)、星號(hào)數(shù)之間的數(shù)學(xué)關(guān)系。我的做法是先在草稿紙上畫一個(gè)5行的菱形把每行的空格數(shù)和星號(hào)數(shù)列成一張小表第1行2個(gè)空格1個(gè)星號(hào)第2行1個(gè)空格3個(gè)星號(hào)第3行0個(gè)空格5個(gè)星號(hào)第4行1個(gè)空格3個(gè)星號(hào)第5行2個(gè)空格1個(gè)星號(hào)然后就能推出公式對(duì)于上半部分第i行需要n - i個(gè)空格和2 * i - 1個(gè)星號(hào)下半部分是上半部分的鏡像。只要掌握這個(gè)規(guī)律寫兩個(gè)循環(huán)嵌套就完事了。這道題還暴露了一個(gè)常見問題很多同學(xué)不會(huì)用printf控制輸出只會(huì)在每行結(jié)束后用printf(\n)換行。實(shí)際上C語言里控制輸出格式最好用的就是printf中的格式串比如printf(%*s, n, )可以輸出n個(gè)空格。雖然考試中不強(qiáng)制用這些技巧但合理使用能讓代碼減少一個(gè)循環(huán)層級(jí)。3.4 排序與結(jié)構(gòu)體把“排名規(guī)則”讀精確有一道典型的結(jié)構(gòu)體排序題在2.23場次中出現(xiàn)了場景是“輸入n個(gè)學(xué)生的學(xué)號(hào)和成績按成績從高到低排序成績相同時(shí)按學(xué)號(hào)從小到大排序”。這個(gè)直接用qsort最省事#include stdio.h #include stdlib.h typedef struct { int id; int score; } Student; int cmp(const void *a, const void *b) { Student *sa (Student *)a; Student *sb (Student *)b; if (sa-score ! sb-score) { return sb-score - sa-score; } else { return sa-id - sb-id; } } int main() { int n, i; Student stu[1005]; scanf(%d, n); for (i 0; i n; i) { scanf(%d%d, stu[i].id, stu[i].score); } qsort(stu, n, sizeof(Student), cmp); for (i 0; i n; i) { printf(%d %d\n, stu[i].id, stu[i].score); } return 0; }這題的坑點(diǎn)有兩處一是排序規(guī)則的優(yōu)先級(jí)必須先判斷成績再判斷學(xué)號(hào)二是在成績相同時(shí)學(xué)號(hào)要按升序排列但成績降序用的是sb-score - sa-score如果寫反了整體順序就會(huì)和題目要求的完全相反。建議考試時(shí)先用三行數(shù)據(jù)手動(dòng)走一遍排序邏輯確認(rèn)比較函數(shù)的返回值方向正確。如果學(xué)校環(huán)境不允許使用qsort個(gè)別老式OJ環(huán)境確實(shí)有這種情況那就需要自己寫冒泡排序。結(jié)構(gòu)體數(shù)組的交換可以直接用Student temp stu[j]; stu[j] stu[j1]; stu[j1] temp;因?yàn)榻Y(jié)構(gòu)體支持整體賦值這一點(diǎn)比數(shù)組方便很多。3.5 遞歸題慢一步想清楚再動(dòng)手最后一道遞歸題往往讓很多人頭疼。以“輸入一個(gè)正整數(shù)n輸出它的二進(jìn)制表示”為例。很多人一上來就想用循環(huán)加數(shù)組的“除2取余”法這當(dāng)然沒錯(cuò)但如果你對(duì)遞歸理解更深刻可以用更簡潔的方式#include stdio.h void toBinary(int n) { if (n 1) { toBinary(n / 2); } printf(%d, n % 2); } int main() { int n; scanf(%d, n); toBinary(n); printf(\n); return 0; }這個(gè)寫法的核心思路是先遞歸處理高位再輸出當(dāng)前位的余數(shù)從而自然實(shí)現(xiàn)倒序輸出省去了數(shù)組反轉(zhuǎn)的邏輯。很多同學(xué)卡在遞歸題不是因?yàn)椴粫?huì)寫遞歸函數(shù)而是沒有先厘清“遞歸到底在干什么”。我的建議是遇到遞歸題先不要寫代碼拿一張草稿紙手動(dòng)展開兩層遞歸調(diào)用看看每個(gè)變量在每一層是什么值、函數(shù)調(diào)用順序是什么樣的確認(rèn)思路沒問題后再動(dòng)手。4. 高頻踩坑與調(diào)試技巧實(shí)錄4.1 三大最讓人無語的編譯錯(cuò)誤每場OJ考試都有同學(xué)因?yàn)榫幾g錯(cuò)誤拿了零分而且往往是同一個(gè)問題反復(fù)出現(xiàn)。最常見的三個(gè)編譯錯(cuò)誤是第一把main函數(shù)寫成void main()。在ANSI C標(biāo)準(zhǔn)里main函數(shù)的返回值類型必須是int寫成void會(huì)在部分OJ環(huán)境下產(chǎn)生編譯警告甚至錯(cuò)誤。正確的寫法永遠(yuǎn)只有int main()和int main(void)兩種。第二缺少頭文件或者用錯(cuò)了頭文件。比如用了memset但沒有#include string.h用了sqrt但沒有#include math.h。在GCC編譯時(shí)有些函數(shù)如果不包含對(duì)應(yīng)頭文件會(huì)報(bào)隱式聲明錯(cuò)誤雖然老式編譯器可能只給warning但考試系統(tǒng)常會(huì)把它升級(jí)為錯(cuò)誤。第三代碼中出現(xiàn)了中文字符。這個(gè)問題特別隱蔽尤其是有同學(xué)喜歡在代碼里寫中文注釋或者不小心把中文標(biāo)點(diǎn)符號(hào)打進(jìn)了代碼里。編譯器會(huì)把注釋之外的非法字符判定成語法錯(cuò)誤排查起來又非常費(fèi)眼神。我的建議是考試期間寫代碼一律不用中文注釋全部寫英文或干脆不寫省得給自己添堵。4.2 運(yùn)行時(shí)錯(cuò)誤段錯(cuò)誤往往和數(shù)組越界有關(guān)“Runtime Error”也是考場上一個(gè)很常見的反饋。出現(xiàn)段錯(cuò)誤segmentation fault時(shí)九成以上的情況是數(shù)組下標(biāo)越界訪問。比如定義了一個(gè)長度為100的數(shù)組但循環(huán)里訪問了a[100]這就會(huì)踩到未分配的內(nèi)存區(qū)域。另外還有一個(gè)很低級(jí)的錯(cuò)誤在循環(huán)里對(duì)數(shù)組賦值時(shí)下標(biāo)變量寫錯(cuò)了。比如for (i 1; i n; i) scanf(%d, a[i]);如果數(shù)組長度剛好是n那a[n]就是越界訪問。很多人習(xí)慣把輸入的下標(biāo)從1開始但數(shù)組定義的長度必須相應(yīng)地加1否則就會(huì)在最后一步越界。4.3 用暴力輸出法快速定位邏輯錯(cuò)誤當(dāng)你拿到“Wrong Answer”調(diào)試思路很重要。OJ系統(tǒng)不會(huì)告訴你哪一個(gè)測試點(diǎn)錯(cuò)了這時(shí)候我會(huì)用“暴力輸出法”來定位問題在代碼關(guān)鍵位置臨時(shí)加幾行printf把中間變量的值打出來對(duì)比手動(dòng)計(jì)算的結(jié)果。比如在排序代碼里每交換一次就打印當(dāng)前數(shù)組的狀態(tài)在循環(huán)代碼里打印每一輪循環(huán)的i值和結(jié)果值。找到邏輯錯(cuò)誤后把臨時(shí)代碼刪掉再提交。這個(gè)方法還有一個(gè)變種構(gòu)造小規(guī)模測試數(shù)據(jù)。比如題目中n的范圍是1到10000但我手動(dòng)構(gòu)造n1、n2、n3這種最小規(guī)模的數(shù)據(jù)以及n10000這種最大規(guī)模的數(shù)據(jù)看程序在邊界情況下是否崩潰、結(jié)果是否合理。邊界數(shù)據(jù)往往就是OJ隱藏測試點(diǎn)中最愛考的部分。4.4 編譯警告也是一種提示信號(hào)我在前面提到編譯警告可能被系統(tǒng)判定為錯(cuò)誤這里再展開說一下。在使用GCC編譯程序時(shí)如果在代碼里寫了沒有使用的變量、類型不匹配的賦值、printf格式符和變量類型不一致等編譯器都會(huì)給出warning。很多人忽略這些warning但warning往往指向邏輯隱患。比如printf(%d, a)這種寫法——a是整數(shù)變量a是它的地址格式符%d和實(shí)際參數(shù)類型不匹配。這類代碼在部分OJ環(huán)境中會(huì)直接判定為編譯錯(cuò)誤就算能編譯通過輸出的也是地址值而不是變量值。所以提交前最好把代碼里的warning數(shù)量降到零這是提升過題穩(wěn)定性的一個(gè)小訣竅。5. 考場答題策略與細(xì)節(jié)制勝法則5.1 時(shí)間分配和答題順序的實(shí)戰(zhàn)方案一場3小時(shí)的考試7道題合理的時(shí)間分配大概是這樣的前兩道熱身題控制在20分鐘內(nèi)完成中間三道題控制在70分鐘內(nèi)完成最后兩道難題先各自花10分鐘審題如果15分鐘內(nèi)沒有思路就先跳過最后留出30分鐘檢查已經(jīng)提交的代碼。這個(gè)節(jié)奏聽起來寬松但實(shí)際上很多同學(xué)在第二、第三道題上花了太多時(shí)間導(dǎo)致最后一道題完全沒有時(shí)間碰。我的個(gè)人經(jīng)驗(yàn)是每道題如果連續(xù)提交三次都是“Wrong Answer”立刻停止糾結(jié)這道題跳到下一題。等所有會(huì)做的題都拿到分了再回來冷靜處理這個(gè)硬骨頭。人在緊張時(shí)容易鉆牛角尖換個(gè)題目做往往能換一種思路。5.2 讀懂題目的三個(gè)關(guān)鍵動(dòng)作很多WA不是因?yàn)榇a寫錯(cuò)而是因?yàn)轭}意理解偏了。我總結(jié)了一套讀題流程第一步先讀輸入描述。搞清楚有幾個(gè)輸入數(shù)據(jù)、每個(gè)數(shù)據(jù)是什么類型、取值范圍是多少、是否有多組輸入。如果題目有多組輸入記得用循環(huán)讀取直到EOF。第二步再讀輸出描述??摧敵鍪且粋€(gè)數(shù)還是一行數(shù)每行之間是否有空行浮點(diǎn)數(shù)要求保留幾位小數(shù)字符串輸出有沒有特定前綴后綴。這些細(xì)節(jié)直接決定printf的格式符。第三步最后讀樣例和樣例解釋。如果題目給了樣例解釋一定要仔細(xì)看因?yàn)樗鼤?huì)告訴你邊界條件的具體處理方式。比如“輸入為0時(shí)結(jié)束”這類條件往往寫在樣例解釋里而不寫在主描述里。5.3 浮點(diǎn)數(shù)比較永遠(yuǎn)不要直接寫等于課程OJ考試?yán)锱紶枙?huì)出現(xiàn)浮點(diǎn)數(shù)題比如求圓的面積、求平均值。這里有一個(gè)必須形成肌肉記憶的知識(shí)點(diǎn)浮點(diǎn)數(shù)不能直接用判斷相等。之所以不能直接用等號(hào)是因?yàn)楦↑c(diǎn)數(shù)在計(jì)算機(jī)里是近似表示的。比如0.1 0.2的結(jié)果并不是精確的0.3而是一個(gè)接近0.3的二進(jìn)制近似值。如果你寫if (sum / n 3.14)很可能因?yàn)榫葐栴}永遠(yuǎn)為假。正確做法是用絕對(duì)誤差小于某個(gè)閾值如1e-6來判斷if (fabs(result - 3.14) 1e-6) { // 視為相等 }另外一個(gè)浮點(diǎn)數(shù)陷阱是輸出精度。題目要求保留兩位小數(shù)時(shí)必須用printf(%.2f, x)這個(gè)格式符表示保留兩位小數(shù)并四舍五入。如果把格式符寫成了%d那輸出的將是一個(gè)完全沒有意義的整數(shù)而且還會(huì)導(dǎo)致后續(xù)輸出錯(cuò)位。5.4 最后一小時(shí)檢查清單比新寫代碼更重要當(dāng)考試進(jìn)入最后一小時(shí)正確的策略不是繼續(xù)挑戰(zhàn)難題而是回頭檢查已經(jīng)提交的代碼。我會(huì)按這個(gè)清單逐項(xiàng)核對(duì)第一檢查數(shù)組大小是否足夠。題目中n的最大范圍是10000數(shù)組就該開成n 5或10005寧可開大也不要開小。第二檢查循環(huán)邊界。循環(huán)里用的是 n還是 n下標(biāo)是從0開始還是從1開始這兩者絕對(duì)不要混用。第三檢查每行輸出末尾是否都有換行符。OJ判題時(shí)即便只是少了末尾的\n也有可能被判為格式錯(cuò)誤。檢查完這些低級(jí)問題再回過頭想有沒有更優(yōu)解法。但這里必須提醒考試中“能通過的代碼”永遠(yuǎn)優(yōu)于“優(yōu)美的代碼”。如果暴力解法能過就不要去優(yōu)化如果已經(jīng)AC了的題目不要再反復(fù)提交求更短代碼浪費(fèi)時(shí)間還容易改出新錯(cuò)誤。5.5 考前最后幾天的復(fù)習(xí)建議距離考試還有一周時(shí)比起刷新題更重要的是把平時(shí)作業(yè)和往年真題里的錯(cuò)題重新做一遍。我當(dāng)年復(fù)習(xí)時(shí)做了一個(gè)錯(cuò)題本專門記錄每一道題為什么錯(cuò)是邊界條件沒處理是數(shù)組越界還是根本沒讀懂題意考前花一晚上把錯(cuò)題本的規(guī)律過一遍比刷十道新題更有效。另外強(qiáng)烈建議在考試前用學(xué)校OJ系統(tǒng)的模擬環(huán)境做一次全真模擬限時(shí)3小時(shí)模擬過程中不開任何聊天工具、不看資料、不切出去搜索。這樣在真正考試時(shí)你才能適應(yīng)那種連續(xù)高強(qiáng)度思考的節(jié)奏。很多人第一次參加OJ考試時(shí)會(huì)覺得眼睛酸、頸椎疼、腦子轉(zhuǎn)不動(dòng)其實(shí)不只是身體問題更重要的是心理上沒有適應(yīng)“長時(shí)間面對(duì)評(píng)測系統(tǒng)”的狀態(tài)。再分享一個(gè)小技巧把常用代碼片段提前準(zhǔn)備好。比如快讀快寫函數(shù)、最大公約數(shù)函數(shù)、結(jié)構(gòu)體比較函數(shù)、日期判斷函數(shù)這些幾乎每年都會(huì)用到。雖然考試系統(tǒng)不支持復(fù)制粘貼外部代碼但你在腦子里或者草稿紙上提前熟悉這些模板的寫法考試時(shí)就可以直接默寫出來節(jié)省大量思考時(shí)間。6. 考后復(fù)盤與長期能力進(jìn)階6.1 分?jǐn)?shù)不是終點(diǎn)復(fù)盤才是收獲考試結(jié)束后很多同學(xué)只看一眼成績就關(guān)掉了OJ頁面這是非常浪費(fèi)的行為。正確的做法是查閱自己每一道題的提交記錄看每一輪錯(cuò)誤反饋分別是什么類型——是編譯錯(cuò)誤、答案錯(cuò)誤還是運(yùn)行超時(shí)。這些反饋信息就是你基礎(chǔ)知識(shí)漏洞的體檢報(bào)告。比如如果你經(jīng)常遇到“Wrong Answer”說明你的邏輯實(shí)現(xiàn)有問題需要加強(qiáng)題目理解和代碼實(shí)現(xiàn)能力如果經(jīng)?!癈ompile Error”說明你的語法基礎(chǔ)不牢固需要回爐基本功如果經(jīng)常“Time Limit Exceeded”說明你的算法復(fù)雜度意識(shí)不夠需要在“暴力解法”和“更優(yōu)解法”之間找到平衡。每次考試后花兩小時(shí)復(fù)盤把這些錯(cuò)誤分類記錄比盲目刷題更能提升水平。你甚至可以統(tǒng)計(jì)一下自己哪類題最容易卡住然后在接下來的練習(xí)里針對(duì)性地補(bǔ)強(qiáng)。6.2 從課程考試到工程能力的三個(gè)跨度程序設(shè)計(jì)基礎(chǔ)這門課是計(jì)算機(jī)相關(guān)專業(yè)的第一門編程課期末考試當(dāng)然很重要但比期末考試更重要的是你從這門課里建立的編程思維模式。我自己后來回想OJ考試推動(dòng)我完成三個(gè)關(guān)鍵轉(zhuǎn)化第一個(gè)轉(zhuǎn)化是從“照著例子敲代碼”到“從零開始設(shè)計(jì)代碼”。課堂演示和課后作業(yè)里很多題目有示例代碼可以參考但考試時(shí)沒有任何參考你必須自己分析問題分解模塊、設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)和控制流。這個(gè)過程就是初級(jí)程序員的基本功。第二個(gè)轉(zhuǎn)化是從“在IDE里看結(jié)果”到“理解自動(dòng)判題機(jī)制”。OJ系統(tǒng)不會(huì)告訴你哪里錯(cuò)了只給你一個(gè)冷冰冰的反饋。你必須學(xué)會(huì)通過有限的反饋信息來推斷程序的行為這本質(zhì)上就是調(diào)試能力的雛形。真正到了工程開發(fā)中面對(duì)幾十萬行代碼中的bug這種“從現(xiàn)象反推原因”的能力會(huì)非常寶貴。第三個(gè)轉(zhuǎn)化是從“能跑就行”到“為邊界而設(shè)計(jì)”。課程OJ考試考察邊界條件本質(zhì)上是培養(yǎng)你全面思考問題的意識(shí)任何輸入范圍、任何特殊情況都在你的設(shè)計(jì)考慮之內(nèi)。工程中的系統(tǒng)崩潰絕大多數(shù)都發(fā)生在邊界條件下能在程序設(shè)計(jì)基礎(chǔ)階段就養(yǎng)成這種意識(shí)后面的路會(huì)順很多。6.3 后續(xù)學(xué)習(xí)路線參考如果你在這門課結(jié)束后發(fā)現(xiàn)自己對(duì)編程還有點(diǎn)興趣建議按這個(gè)順序往下走第一階段系統(tǒng)刷一遍洛谷或PAT的基礎(chǔ)題單把排序、查找、字符串、枚舉這些基礎(chǔ)算法練熟練透第二階段學(xué)一點(diǎn)C的STL掌握vector、string、sort、map這幾個(gè)常用容器它們能極大提升編碼效率第三階段開始學(xué)數(shù)據(jù)結(jié)構(gòu)系統(tǒng)的理解和實(shí)現(xiàn)線性表、棧、隊(duì)列、二叉樹、圖這些核心結(jié)構(gòu)。如果將來想往競賽方向發(fā)展藍(lán)橋杯、ACM-ICPC、團(tuán)體程序設(shè)計(jì)天梯賽都是很好的鍛煉平臺(tái)如果只是想學(xué)好課程、應(yīng)付后續(xù)考試那把教材里的課后習(xí)題全部吃透就足夠了。不管走哪條路認(rèn)真對(duì)待每一次OJ刷題和每一場模擬考試水平一定不會(huì)差。7. 寫在最后的一點(diǎn)真實(shí)體會(huì)從助教到考前輔導(dǎo)我前前后后接觸了幾百名備考《程序設(shè)計(jì)基礎(chǔ)》OJ考試的同學(xué)發(fā)現(xiàn)高分組和低分組之間的差別往往不在“誰更聰明”而在“誰更細(xì)致”。那些能滿分通過的同學(xué)并不是每一種算法都比別人精通而是他們幾乎從不犯低級(jí)錯(cuò)誤不會(huì)把循環(huán)邊界寫錯(cuò)不會(huì)搞混數(shù)組下標(biāo)不會(huì)忘記輸出末尾的換行符不會(huì)在浮點(diǎn)數(shù)比較上踩坑。這些細(xì)節(jié)說起來都很瑣碎但每一屆都有大量因?yàn)樾∈д`丟掉十幾二十分的人。2.23這場考試暴露出的問題和我們在任何一場程序設(shè)計(jì)考試中暴露出的問題本質(zhì)上都是一樣的代碼能力是在一次次錯(cuò)誤中成長的期末考試只是其中一次重要的檢驗(yàn)。如果你能在備考階段養(yǎng)成“多想一步邊界條件、多看一眼輸出格式、多驗(yàn)證一組測試數(shù)據(jù)”的習(xí)慣那不管考哪一場你都大概率能拿到和你真實(shí)水平匹配的分?jǐn)?shù)。最后再分享一個(gè)我自己的小習(xí)慣考試發(fā)卷后的前五分鐘不去寫任何代碼先把所有題目通讀一遍用腦子快速判斷每道題的難度和大致思路在草稿紙上記下每道題預(yù)估的耗時(shí)。這樣一個(gè)簡單的前置動(dòng)作能讓你在整個(gè)答題過程中時(shí)刻清楚自己的節(jié)奏和時(shí)間余量極大減少最后十分鐘的慌亂感。祝所有正在備考的同學(xué)都能一次過題順利拿高分。