算法到多線程并發(fā)實(shí)戰(zhàn))
校招季又到了每年這時(shí)候總有一堆同學(xué)在后臺(tái)問(wèn)我要各家大廠的編程題匯總。今年正好整理電腦文件時(shí)翻到了2020年平安科技技術(shù)崗校招的部分編程題筆記當(dāng)時(shí)我完整參加了平安科技的校招流程筆試、技術(shù)面、HR面一路走下來(lái)積累了不少一手資料。今年把這些題目重新梳理了一遍結(jié)合當(dāng)年的解題思路、踩過(guò)的坑和一些復(fù)盤(pán)心得整理成一篇可以直接拿來(lái)備戰(zhàn)的參考。無(wú)論你是準(zhǔn)備投平安科技還是想了解金融科技方向的技術(shù)考察重點(diǎn)這篇都值得花幾分鐘讀完。先說(shuō)下平安科技筆試的總體感受它和互聯(lián)網(wǎng)大廠字節(jié)、阿里那種的筆試風(fēng)格有明顯的區(qū)別。平安作為金融科技公司技術(shù)筆試更看重邏輯嚴(yán)密性、代碼規(guī)范性和對(duì)邊界條件的把控而不是純粹比拼算法競(jìng)賽技巧。題目以中檔難度為主很少出現(xiàn)超級(jí)hard的偏題怪題但會(huì)在看似簡(jiǎn)單的題目里埋一些細(xì)節(jié)陷阱這點(diǎn)我在后面的題目拆解中會(huì)詳細(xì)說(shuō)。1. 2020年平安科技校招編程題的整體風(fēng)格分析1.1 題型分布與考察方向平安科技2020屆校招技術(shù)崗的編程題整體分為兩大類(lèi)一類(lèi)是純算法題占總分的絕大部分比重另一類(lèi)是場(chǎng)景應(yīng)用題會(huì)和實(shí)際業(yè)務(wù)結(jié)合考察代碼落地能力。字符串/數(shù)組操作這類(lèi)題占比最高大概能到40%左右題目本身不難但非??简?yàn)代碼的嚴(yán)謹(jǐn)性比如指針越界、空指針、字符編碼處理等。數(shù)據(jù)結(jié)構(gòu)應(yīng)用重點(diǎn)考察棧、隊(duì)列、哈希表、鏈表二叉樹(shù)也會(huì)涉及但樹(shù)這塊不會(huì)太深基本停留在遍歷和基礎(chǔ)性質(zhì)判斷層面。動(dòng)態(tài)規(guī)劃/貪心算法這類(lèi)題有固定套路平安的出題特點(diǎn)是不繞彎子狀態(tài)轉(zhuǎn)移方程比較直接關(guān)鍵看你能不能快速識(shí)別出題型并寫(xiě)出干凈的轉(zhuǎn)移邏輯。多線程/并發(fā)場(chǎng)景這是平安區(qū)別于其他互聯(lián)網(wǎng)公司的一個(gè)特色考點(diǎn)。因?yàn)闃I(yè)務(wù)系統(tǒng)涉及交易、風(fēng)控等場(chǎng)景對(duì)并發(fā)編程的考察不是死記API而是給一個(gè)實(shí)際業(yè)務(wù)場(chǎng)景讓你實(shí)現(xiàn)線程安全的代碼。數(shù)據(jù)庫(kù)SQL題技術(shù)崗筆試偶爾會(huì)出現(xiàn)一道SQL場(chǎng)景題考察基本查詢(xún)、聯(lián)表、聚合難度不大但要求寫(xiě)出的SQL能正確應(yīng)對(duì)邊界查詢(xún)條件。我手里整理的這批題目主要覆蓋前四類(lèi)。原題的具體描述經(jīng)過(guò)這三年已經(jīng)有了不少流傳版本我按自己記憶中比較接近原意的描述重新整理并配上完整的解題思路和實(shí)現(xiàn)代碼。1.2 難度梯度與選拔邏輯把平安的編程題整體過(guò)一遍你會(huì)發(fā)現(xiàn)它的難度分布是一個(gè)典型的金字塔結(jié)構(gòu)從基礎(chǔ)到進(jìn)階層層遞進(jìn)?;A(chǔ)題占比約50%通常是一道字符串處理或簡(jiǎn)單模擬題例如反轉(zhuǎn)字符串、統(tǒng)計(jì)字符頻率、數(shù)組去重。這類(lèi)題主要篩掉完全沒(méi)準(zhǔn)備過(guò)的裸考選手只要刷過(guò)50道LeetCode簡(jiǎn)單題就能穩(wěn)拿。但這類(lèi)題也不是無(wú)腦拿分的我在整理時(shí)發(fā)現(xiàn)平安特別喜歡在基礎(chǔ)題上設(shè)置“隱藏條件”比如要求不使用額外空間、要求時(shí)間復(fù)雜度O(n)、要求原地修改等這些附加約束才是區(qū)分度的關(guān)鍵。拔高題占比約30%涉及哈希表、單調(diào)棧、雙指針等經(jīng)典技巧或者是動(dòng)態(tài)規(guī)劃的入門(mén)級(jí)別。這類(lèi)題需要你形成條件反射式的解題直覺(jué)看到題目就能快速定位到對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)或算法模型。壓軸題占比約20%通常是一道多線程并發(fā)場(chǎng)景題或者一道綜合性較強(qiáng)的應(yīng)用模擬題。這類(lèi)題沒(méi)有標(biāo)準(zhǔn)答案判分看重的是你的代碼風(fēng)格、線程安全處理、以及異常邊界處理是否到位。坦白講我看到很多同學(xué)在這類(lèi)題上直接空著其實(shí)是很可惜的。即使不能完全跑通把線程安全、鎖、并發(fā)控制的思路寫(xiě)出來(lái)面試官也會(huì)給一部分步驟分。2. 編程題逐題拆解與解題思路2.1 字符串壓縮考察頻率最高的基礎(chǔ)題這道題是平安筆試的高頻題出題形式很經(jīng)典給定一個(gè)字符串將連續(xù)重復(fù)的字符壓縮成“字符重復(fù)次數(shù)”的形式。例如輸入aaabbc輸出a3b2c1。如果壓縮后的字符串長(zhǎng)度不小于原字符串則返回原字符串。很多人在這道題上丟分不是因?yàn)椴粫?huì)寫(xiě)而是因?yàn)檫吔鐥l件處理不到位。我先給一份相對(duì)完整的參考實(shí)現(xiàn)def compress_string(s: str) - str: if not s: return res [] count 1 for i in range(1, len(s)): if s[i] s[i - 1]: count 1 else: res.append(s[i - 1] str(count)) count 1 # 處理最后一組字符 res.append(s[-1] str(count)) compressed .join(res) return compressed if len(compressed) len(s) else s這段代碼的核心思路是用一次線性掃描統(tǒng)計(jì)相鄰相同字符的數(shù)量維護(hù)一個(gè)count變量每當(dāng)字符變化時(shí)就把前一個(gè)字符及其次數(shù)寫(xiě)入結(jié)果。最后別忘了處理末尾字符這是一個(gè)非常典型的遺漏點(diǎn)。再看剛才提到的兩個(gè)陷阱空字符串的情況。很多人上來(lái)就res.append(s[0])空字符串直接越界報(bào)錯(cuò)。壓縮后長(zhǎng)度不小于原字符串時(shí)需要返回原串。這說(shuō)明題目要求的是“無(wú)損壓縮”如果不能縮短則不壓縮這是業(yè)務(wù)系統(tǒng)中常見(jiàn)的邏輯——避免無(wú)效轉(zhuǎn)換。我在面試復(fù)盤(pán)時(shí)和幾個(gè)一起進(jìn)面試的同學(xué)交流過(guò)這道題最大的問(wèn)題其實(shí)是很多人忘了加上最后那行return compressed if len(compressed) len(s) else s。去掉這行代碼在細(xì)節(jié)測(cè)試用例上就會(huì)出錯(cuò)。這種“簡(jiǎn)單題里暗藏玄機(jī)”的出題風(fēng)格幾乎貫穿平安技術(shù)筆試的全程。2.2 股票買(mǎi)賣(mài)最佳時(shí)機(jī)動(dòng)態(tài)規(guī)劃基礎(chǔ)型第二類(lèi)高頻題是股票買(mǎi)賣(mài)類(lèi)問(wèn)題。2020年考的是最簡(jiǎn)單的一個(gè)版本給定一個(gè)數(shù)組第i個(gè)元素是第i天的股票價(jià)格只允許完成一筆交易買(mǎi)入一次、賣(mài)出一次設(shè)計(jì)算法獲得最大利潤(rùn)。這題最直觀的思路是雙重循環(huán)枚舉買(mǎi)入日和賣(mài)出日但時(shí)間復(fù)雜度是O(n^2)在數(shù)據(jù)規(guī)模大時(shí)會(huì)超時(shí)。務(wù)實(shí)的做法是動(dòng)態(tài)規(guī)劃或者一次遍歷維護(hù)最小值。參考代碼如下def max_profit(prices) - int: if not prices or len(prices) 2: return 0 min_price prices[0] max_profit 0 for price in prices[1:]: if price min_price: min_price price else: max_profit max(max_profit, price - min_price) return max_profit核心思路是遍歷價(jià)格數(shù)組時(shí)不斷更新歷史最低價(jià)min_price同時(shí)計(jì)算當(dāng)前價(jià)格與歷史最低價(jià)的差值更新最大利潤(rùn)。這里的隱含邏輯是要獲得最大收益一定是在最低點(diǎn)買(mǎi)入、在之后的某一天賣(mài)出所以只要跟蹤最低點(diǎn)就能確保每一步的收益計(jì)算都是基于最優(yōu)買(mǎi)入時(shí)機(jī)。這道題值得注意的點(diǎn)是題目明確說(shuō)“只允許完成一筆交易”所以不需要考慮多次買(mǎi)賣(mài)的疊加。有些同學(xué)會(huì)條件反射地去套“累加所有上升段”的解法——那是無(wú)窮次交易版本的思路在2020年的題目中會(huì)直接算錯(cuò)。我在筆試時(shí)也差點(diǎn)踩了這個(gè)坑讀題的時(shí)候把“一筆交易”四個(gè)字圈出來(lái)是這類(lèi)題最有效的防錯(cuò)方式。2.3 鏈表反轉(zhuǎn)數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)真題鏈表的考察在平安筆試中頻率不算低因?yàn)樗芡瑫r(shí)考察指針操作和邊界控制能力。2020年的題目是經(jīng)典的“反轉(zhuǎn)單鏈表”給定一個(gè)單鏈表的頭節(jié)點(diǎn)將其反轉(zhuǎn)返回新鏈表的頭節(jié)點(diǎn)。這道題的標(biāo)準(zhǔn)解法有兩種迭代法和遞歸法。筆試時(shí)我建議用迭代法因?yàn)檫f歸法需要理解遞歸棧的展開(kāi)過(guò)程在線上筆試那種緊張環(huán)境下容易寫(xiě)錯(cuò)而且遞歸深度過(guò)深還會(huì)造成棧溢出。迭代法參考代碼class ListNode: def __init__(self, val0, nextNone): self.val val self.next next def reverse_list(head: ListNode) - ListNode: prev None curr head while curr: next_node curr.next # 先保存下一個(gè)節(jié)點(diǎn) curr.next prev # 反轉(zhuǎn)指針 prev curr # 移動(dòng)prev curr next_node # 移動(dòng)curr return prev這里容易出錯(cuò)的點(diǎn)有兩個(gè)第一在while循環(huán)中第一步必須是next_node curr.next否則一旦執(zhí)行curr.next prev原鏈表的下一個(gè)節(jié)點(diǎn)就丟失了。第二循環(huán)結(jié)束后prev指向的是新鏈表的頭節(jié)點(diǎn)而head此時(shí)指向的是原鏈表的尾節(jié)點(diǎn)即新鏈表的尾如果返回head就全錯(cuò)了。在實(shí)際筆試中這類(lèi)題通常會(huì)給完整的鏈表定義和輸入格式。我建議在寫(xiě)代碼前先在草稿紙上畫(huà)一下鏈表的指針變化圖三個(gè)節(jié)點(diǎn)就夠把每一步prev、curr、next_node的指向關(guān)系畫(huà)清楚寫(xiě)起代碼來(lái)會(huì)順暢很多。這是我在多次模擬筆試后總結(jié)出來(lái)的經(jīng)驗(yàn)比空想指針的變化要高效得多。2.4 多線程交替打印平安的特色考題接下來(lái)這道題就有點(diǎn)平安特色了。題目要求創(chuàng)建兩個(gè)線程一個(gè)線程負(fù)責(zé)打印奇數(shù)另一個(gè)線程負(fù)責(zé)打印偶數(shù)兩個(gè)線程交替輸出1到100的數(shù)字。這道題在互聯(lián)網(wǎng)大廠筆試中不算常見(jiàn)但在金融科技公司的筆試中出現(xiàn)的頻率不低因?yàn)榻灰紫到y(tǒng)、賬務(wù)系統(tǒng)中有大量類(lèi)似的并發(fā)協(xié)作場(chǎng)景。核心考點(diǎn)是線程通信和同步考察你是否能熟練使用鎖或信號(hào)量控制線程的執(zhí)行順序。參考實(shí)現(xiàn)Python版本import threading def print_odd(): for i in range(1, 101, 2): lock_even.acquire() print(i) lock_odd.release() def print_even(): for i in range(2, 101, 2): lock_odd.acquire() print(i) lock_even.release() lock_odd threading.Lock() lock_even threading.Lock() lock_even.acquire() # 初始讓偶數(shù)線程等待 t1 threading.Thread(targetprint_odd) t2 threading.Thread(targetprint_even) t1.start() t2.start() t1.join() t2.join()這個(gè)實(shí)現(xiàn)的核心思想是使用兩把鎖交替獲取和釋放形成嚴(yán)格的執(zhí)行順序。初始狀態(tài)讓偶數(shù)線程的鎖處于占用狀態(tài)確保奇數(shù)線程先執(zhí)行。每打印一個(gè)數(shù)后釋放對(duì)方的鎖同時(shí)阻塞自己的鎖這樣線程之間就形成了交替執(zhí)行的節(jié)奏。這個(gè)方案的關(guān)鍵在于兩把鎖的初始狀態(tài)設(shè)置很多人在這個(gè)細(xì)節(jié)上出錯(cuò)導(dǎo)致程序死鎖或者順序錯(cuò)亂。我當(dāng)年在筆試時(shí)就在草稿紙上仔細(xì)推演了每個(gè)線程在每一步的鎖狀態(tài)變化確保沒(méi)有死鎖風(fēng)險(xiǎn)后才落筆。另外如果筆試環(huán)境支持Java語(yǔ)言用wait()和notify()實(shí)現(xiàn)也是常見(jiàn)的做法。但要注意wait()必須在同步代碼塊中調(diào)用否則會(huì)拋IllegalMonitorStateException。我在幫一個(gè)師弟review代碼時(shí)就看到過(guò)這個(gè)錯(cuò)誤他以為只要調(diào)了wait()線程就會(huì)自動(dòng)讓出鎖完全沒(méi)有意識(shí)到同步塊的前提條件。這種小錯(cuò)誤在筆試中很致命因?yàn)榕芯硐到y(tǒng)會(huì)直接把代碼跑掛。2.5 兩個(gè)數(shù)組的交集哈希表經(jīng)典應(yīng)用題這是一道非常典型的哈希表應(yīng)用題平安也喜歡在筆試中考察這類(lèi)數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ)應(yīng)用。題目描述是給定兩個(gè)數(shù)組編寫(xiě)一個(gè)函數(shù)來(lái)計(jì)算它們的交集輸出結(jié)果中每個(gè)元素出現(xiàn)的次數(shù)應(yīng)與元素在兩個(gè)數(shù)組中出現(xiàn)的次數(shù)一致。參考實(shí)現(xiàn)from collections import Counter def intersect(nums1, nums2): if not nums1 or not nums2: return [] counter1 Counter(nums1) result [] for num in nums2: if counter1.get(num, 0) 0: result.append(num) counter1[num] - 1 return result思路很簡(jiǎn)單先用哈希表統(tǒng)計(jì)第一個(gè)數(shù)組中每個(gè)元素出現(xiàn)的次數(shù)再遍歷第二個(gè)數(shù)組每遇到一個(gè)在哈希表中還有余量的元素就加入結(jié)果并將計(jì)數(shù)減一。這樣可以正確處理重復(fù)元素的情況。這道題的進(jìn)階版本是如果數(shù)組已經(jīng)有序如何優(yōu)化空間復(fù)雜度那就用雙指針解法兩個(gè)指針?lè)謩e指向兩個(gè)數(shù)組的開(kāi)頭比較當(dāng)前元素大小相等則加入結(jié)果不相等則移動(dòng)較小元素所在的指針。這個(gè)解法的時(shí)間復(fù)雜度是O(nm)空間復(fù)雜度O(1)在筆試中如果能把這兩種解法都寫(xiě)出來(lái)會(huì)是一個(gè)非常加分的展示。我在復(fù)盤(pán)時(shí)注意到平安的面試官比較欣賞“能給出多種解法并分析取舍”的候選人這比只寫(xiě)一種能跑的解法要立體得多。2.6 從上到下打印二叉樹(shù)BFS層序遍歷二叉樹(shù)層次遍歷是校招筆試中的常青樹(shù)平安2020年也考了一道變種題從上到下按層打印二叉樹(shù)同一層的節(jié)點(diǎn)按從左到右的順序打印每一層打印到一行。這道題的本質(zhì)就是二叉樹(shù)的廣度優(yōu)先搜索BFS最通用的框架是用隊(duì)列輔助實(shí)現(xiàn)。代碼框架如下from collections import deque def level_order(root): if not root: return [] result [] queue deque([root]) while queue: level_size len(queue) level_nodes [] for _ in range(level_size): node queue.popleft() level_nodes.append(node.val) if node.left: queue.append(node.left) if node.right: queue.append(node.right) result.append(level_nodes) return result這里的關(guān)鍵技巧在于每輪循環(huán)開(kāi)始時(shí)先用level_size len(queue)鎖定當(dāng)前層的節(jié)點(diǎn)數(shù)。因?yàn)樵诒闅v過(guò)程中隊(duì)列中會(huì)不斷加入下一層的節(jié)點(diǎn)如果不提前鎖定層大小就無(wú)法區(qū)分當(dāng)前層和下一層輸出的結(jié)果就會(huì)變成一維數(shù)組而不是分層的二維數(shù)組。我見(jiàn)過(guò)不少同學(xué)在筆試時(shí)寫(xiě)出無(wú)法正確分層的版本原因就是沒(méi)有理解level_size的作用。其實(shí)這個(gè)技巧在LeetCode 102題中有非常詳細(xì)的推導(dǎo)過(guò)程刷過(guò)這道題的人基本都能順利寫(xiě)出來(lái)。所以我在總結(jié)中經(jīng)常對(duì)學(xué)弟學(xué)妹說(shuō)算法題的復(fù)習(xí)不在于數(shù)量而在于把每個(gè)基礎(chǔ)題型的框架吃透這樣遇到變形題才能快速遷移。3. 完整實(shí)操線上筆試流程與代碼提交技巧3.1 平安的筆試環(huán)境與平臺(tái)操作要點(diǎn)2020年平安科技的線上筆試用的是第三方在線評(píng)測(cè)平臺(tái)整體體驗(yàn)和??途W(wǎng)、LeetCode的在線評(píng)測(cè)非常類(lèi)似。筆試時(shí)間大概90分鐘題量在3到5道之間每道題的分值不同。編程語(yǔ)言選擇平臺(tái)支持C、Java、Python等主流語(yǔ)言。如果沒(méi)特別說(shuō)明我建議優(yōu)先選Python因?yàn)榇a量更少、調(diào)試更快尤其在處理字符串和數(shù)組這類(lèi)題目時(shí)Python的內(nèi)置方法能節(jié)省大量時(shí)間。代碼補(bǔ)全方式筆試平臺(tái)的代碼編輯器通常不提供自動(dòng)補(bǔ)全而且縮進(jìn)有時(shí)候會(huì)出問(wèn)題。建議提前在本地IDE把所有題目的代碼框架寫(xiě)好然后復(fù)制到筆試平臺(tái)。復(fù)制粘貼后一定要重新檢查一遍縮進(jìn)和括號(hào)避免格式問(wèn)題導(dǎo)致的低級(jí)錯(cuò)誤。輸入輸出格式平安的筆試平臺(tái)采用的是標(biāo)準(zhǔn)輸入輸出模式。換句話說(shuō)判卷系統(tǒng)不會(huì)調(diào)用你的函數(shù)而是把你的程序當(dāng)作獨(dú)立進(jìn)程運(yùn)行從標(biāo)準(zhǔn)輸入讀取測(cè)試數(shù)據(jù)從標(biāo)準(zhǔn)輸出讀取結(jié)果。這個(gè)和LeetCode的“函數(shù)補(bǔ)全”模式完全不同。很多第一次接觸這種模式的同學(xué)會(huì)在這里吃大虧在本地調(diào)試好好的代碼一提交就是“格式錯(cuò)誤”。一個(gè)典型的例子是輸入一個(gè)整數(shù)數(shù)組平臺(tái)可能是用空格分隔的一行字符串。你需要在程序里手動(dòng)處理input()讀入的字符串用split()轉(zhuǎn)換成列表而不能直接假設(shè)系統(tǒng)已經(jīng)幫你處理好了數(shù)據(jù)結(jié)構(gòu)。我當(dāng)時(shí)總結(jié)了一個(gè)標(biāo)準(zhǔn)的輸入讀取模板import sys def main(): data sys.stdin.read().strip().split() if not data: return # 根據(jù)題目要求解析例如第一個(gè)數(shù)是數(shù)組長(zhǎng)度 n int(data[0]) arr list(map(int, data[1:1n])) # 業(yè)務(wù)邏輯... print(result) if __name__ __main__: main()使用sys.stdin.read()一次性讀入所有內(nèi)容再統(tǒng)一用split()切分能避免多行輸入時(shí)input()的麻煩。這個(gè)模板我后來(lái)在多次筆試中反復(fù)使用省了不少時(shí)間。建議準(zhǔn)備參加筆試的同學(xué)把這類(lèi)標(biāo)準(zhǔn)輸入輸出的模板背熟這屬于考前性?xún)r(jià)比最高的準(zhǔn)備工作。3.2 一個(gè)完整題目的全流程調(diào)試記錄以股票買(mǎi)賣(mài)這道題為例我完整演示一下筆試時(shí)的做題流程和調(diào)試思路。第一步先讀題圈出關(guān)鍵限制條件。題目給了數(shù)組長(zhǎng)度范圍假設(shè)是1 prices.length 10^5。這意味著算法的時(shí)間復(fù)雜度必須控制在O(n)或者O(nlogn)級(jí)別O(n^2)的暴力解法一定會(huì)超時(shí)。第二步在草稿紙上推導(dǎo)思路。為什么可以用一次遍歷完成核心在于我們只需要知道到當(dāng)前天為止的歷史最低價(jià)以及當(dāng)前價(jià)格減去歷史最低價(jià)所得到的潛在收益。這些信息可以在一次遍歷中持續(xù)維護(hù)不需要回頭去枚舉每一天的買(mǎi)入價(jià)。第三步寫(xiě)出代碼框架后用題目給的示例數(shù)據(jù)做一次人工推演。prices [7, 1, 5, 3, 6, 4]初始化min_price 7max_profit 0。遍歷到1小于min_price更新min_price 1。遍歷到55 - 1 4更新max_profit 4。遍歷到33 - 1 2小于4不更新。遍歷到66 - 1 5更新max_profit 5。遍歷到44 - 1 3小于5不更新。輸出結(jié)果為5和預(yù)期一致。這一步人工走查非常管用能提前發(fā)現(xiàn)邏輯錯(cuò)誤避免提交后反復(fù)試錯(cuò)浪費(fèi)時(shí)間。第四步考慮到邊界情況。數(shù)組只有1個(gè)元素時(shí)沒(méi)有合法的買(mǎi)賣(mài)操作應(yīng)該返回0??諗?shù)組也返回0。這些情況在代碼中都有對(duì)應(yīng)的處理邏輯。第五步點(diǎn)擊提交查看評(píng)測(cè)結(jié)果。如果有失敗的測(cè)試用例平臺(tái)通常會(huì)返回錯(cuò)誤類(lèi)型和部分測(cè)試數(shù)據(jù)。我在筆試時(shí)遇到過(guò)一次因?yàn)闆](méi)處理空數(shù)組導(dǎo)致IndexError的情況當(dāng)時(shí)就是根據(jù)評(píng)測(cè)反饋快速定位并修復(fù)的。整體來(lái)說(shuō)有了清晰的做題流程3道編程題中至少能穩(wěn)拿2道題的全部分?jǐn)?shù)另外一道壓軸題能寫(xiě)出框架就能拿部分分?jǐn)?shù)整體筆試通過(guò)基本沒(méi)有太大懸念。3.3 文本輸出格式的細(xì)節(jié)技巧還有一個(gè)非常容易被忽視的細(xì)節(jié)輸出格式。很多在線判題系統(tǒng)對(duì)輸出格式的檢查是“非對(duì)即錯(cuò)”的多一個(gè)空格、少一個(gè)換行都可能導(dǎo)致Wrong Answer。我見(jiàn)過(guò)最典型的案例是要求輸出“每個(gè)數(shù)字占一行”結(jié)果有同學(xué)把所有數(shù)字用空格連接成一行輸出導(dǎo)致全錯(cuò)?;蛘咭筝敵鼋Y(jié)果末尾不能有多余空格結(jié)果用了 .join(map(str, arr))導(dǎo)致最后一組數(shù)據(jù)后多了一個(gè)空格同樣被判錯(cuò)。這里分享一個(gè)穩(wěn)妥的輸出格式方案需要輸出一個(gè)數(shù)組時(shí)優(yōu)先使用print( .join(map(str, result)))這樣能確保元素之間只有一個(gè)空格且末尾沒(méi)有多余空格。如果需要每個(gè)元素占一行用print(\n.join(map(str, result)))。如果需要輸出列表直接用print(result)也是可以的但要注意??突蛸惔a這類(lèi)平臺(tái)的Python版本可能不完全一致直接打印列表時(shí)使用的分隔符可能有差異。保險(xiǎn)起見(jiàn)還是手動(dòng)處理格式更穩(wěn)妥。我在幫助學(xué)弟學(xué)妹們復(fù)盤(pán)筆試的時(shí)候發(fā)現(xiàn)輸出格式導(dǎo)致的失分率出奇的高幾乎每?jī)蓚€(gè)人里就有一個(gè)人因?yàn)楦袷絾?wèn)題丟過(guò)分。這個(gè)細(xì)節(jié)雖然在學(xué)校的大作業(yè)里不扣分但在線上筆試中就是實(shí)打?qū)嵉目鄯贮c(diǎn)需要在考前就形成正確的輸出習(xí)慣。4. 常見(jiàn)問(wèn)題與備考建議速查4.1 編程題高頻問(wèn)題排查記錄我根據(jù)自己的筆試經(jīng)驗(yàn)和多次復(fù)盤(pán)整理了下面這張高頻問(wèn)題速查表覆蓋了大多數(shù)同學(xué)在在線筆試中遇到的典型坑。問(wèn)題類(lèi)型典型表現(xiàn)排查思路與解決方案輸入解析錯(cuò)誤ValueError或IndexError確認(rèn)是用sys.stdin.read()還是input()明確輸入是否包含多行、是否有空行輸出格式不符提示W(wǎng)rong Answer但本地正確檢查結(jié)尾是否有空格、是否缺少換行、每行輸出值是否用對(duì)分隔符空值/邊界值未處理傳入空數(shù)組時(shí)崩潰寫(xiě)代碼前先明確邊界條件給函數(shù)入口加if not ...的保護(hù)判斷遞歸棧溢出大數(shù)據(jù)量時(shí)RecursionError優(yōu)先用迭代解法避免使用遞歸遍歷大數(shù)組或大深度樹(shù)結(jié)構(gòu)哈希表修改沖突RuntimeError: dictionary changed size during iteration遍歷哈希表時(shí)不要直接增刪元素先收集需要操作的key循環(huán)結(jié)束后再統(tǒng)一處理Python縮進(jìn)錯(cuò)亂粘貼后運(yùn)行報(bào)IndentationError寫(xiě)完代碼后全選格式化或者從本地復(fù)制時(shí)使用空格縮進(jìn)而非Tab這張表我在每次考前都會(huì)讓自己過(guò)一遍。尤其是“哈希表遍歷時(shí)修改”這個(gè)坑在校招筆試的查重、頻率統(tǒng)計(jì)類(lèi)題目中特別常見(jiàn)。很多場(chǎng)景下你需要遍歷哈希表并刪除某些不滿(mǎn)足條件的鍵值對(duì)直接刪會(huì)拋異常正確做法是先記錄需要?jiǎng)h除的鍵遍歷結(jié)束后再統(tǒng)一刪除。4.2 平安科技筆試的真實(shí)時(shí)間分配策略90分鐘做3到5道題時(shí)間看起來(lái)還算充裕但如果前面某道題卡住了后面就會(huì)很被動(dòng)。我的建議是拿到卷子后先把所有題目從頭到尾讀一遍給每道題標(biāo)注難度等級(jí)和預(yù)估時(shí)間然后從最簡(jiǎn)單的題目開(kāi)始做。具體的時(shí)間分配策略是前10分鐘通讀所有題目標(biāo)注哪些是必拿分的簡(jiǎn)單題哪些是需要思考的中等題哪些是最后攻堅(jiān)的壓軸題。60到70分鐘集中精力做簡(jiǎn)單題和中等題。簡(jiǎn)單題一次通過(guò)率要爭(zhēng)取100%中等題如果一次寫(xiě)不出完整解法先把思路寫(xiě)清楚再把核心代碼寫(xiě)出來(lái)拿到大部分測(cè)試用例的分?jǐn)?shù)。剩下10到20分鐘攻壓軸題。即使寫(xiě)不出完整版本也要把題目中涉及的線程安全思路、鎖模型、異常處理框架寫(xiě)出來(lái)讓判卷人看到你有完整的工程思維。還有一個(gè)實(shí)際經(jīng)驗(yàn)如果某道題卡了15分鐘還沒(méi)思路果斷跳過(guò)先把后面能拿的分拿上。在線筆試是分測(cè)試點(diǎn)給分的一道題全錯(cuò)和完全沒(méi)做的區(qū)別不大但后面簡(jiǎn)單題的全分卻是實(shí)實(shí)在在的。我見(jiàn)過(guò)太多同學(xué)在壓軸題上死活憋不出來(lái)結(jié)果前面的簡(jiǎn)單題代碼都來(lái)不及寫(xiě)完最后總分一塌糊涂。4.3 針對(duì)平安校招方向的筆試備考建議結(jié)合平安科技的業(yè)務(wù)方向金融科技、保險(xiǎn)科技、智慧城市等在備考時(shí)除了常規(guī)刷題我建議額外關(guān)注以下幾個(gè)方向字符串處理的編碼規(guī)范金融系統(tǒng)中有大量賬號(hào)、身份證號(hào)、手機(jī)號(hào)等敏感數(shù)據(jù)的處理和脫敏筆試中的字符串題往往就是這些業(yè)務(wù)場(chǎng)景的簡(jiǎn)化版。注意字符編碼問(wèn)題Python3中字符串默認(rèn)是Unicode但在某些在線平臺(tái)中可能需要對(duì)中文字符做額外處理。線程安全與并發(fā)控制平安的核心系統(tǒng)對(duì)并發(fā)安全要求極高筆試中出現(xiàn)多線程交替打印、模擬轉(zhuǎn)賬等題目并非偶然。建議熟練掌握Lock、RLock、Semaphore、Condition等并發(fā)原語(yǔ)并能解釋它們之間的區(qū)別和適用場(chǎng)景。數(shù)據(jù)庫(kù)基礎(chǔ)有些崗位的筆試會(huì)加入SQL題尤其是后端開(kāi)發(fā)、數(shù)據(jù)開(kāi)發(fā)方向。基本的JOIN、GROUP BY、HAVING、子查詢(xún)是必須掌握的建議把常見(jiàn)的查詢(xún)場(chǎng)景寫(xiě)一遍。業(yè)務(wù)場(chǎng)景邏輯題平安筆試中也出現(xiàn)過(guò)類(lèi)似“根據(jù)交易流水判斷是否存在異常交易”的簡(jiǎn)化場(chǎng)景題這類(lèi)題目本質(zhì)是模擬題關(guān)鍵在于設(shè)計(jì)清晰的數(shù)據(jù)結(jié)構(gòu)和邏輯流程。不要急于寫(xiě)代碼先在草稿紙上畫(huà)清楚狀態(tài)流轉(zhuǎn)再轉(zhuǎn)換成代碼。時(shí)間規(guī)劃上如果還有一個(gè)月準(zhǔn)備前兩周按模板刷LeetCode高頻題字符串、數(shù)組、哈希表、DP入門(mén)、二叉樹(shù)遍歷第三周開(kāi)始做模擬筆試嚴(yán)格按照90分鐘時(shí)限在牛客或賽碼平臺(tái)進(jìn)行訓(xùn)練最后一周重點(diǎn)復(fù)習(xí)自己容易出錯(cuò)的知識(shí)點(diǎn)和題目類(lèi)型。5. 2020年壓軸題深挖多線程并發(fā)協(xié)作的完整思路延伸5.1 從交替打印擴(kuò)展到生產(chǎn)者消費(fèi)者模型前面提到多線程交替打印是一道很有平安特色的題但在實(shí)際判卷中這道題經(jīng)常會(huì)出現(xiàn)一個(gè)加強(qiáng)版在交替打印的基礎(chǔ)上要求實(shí)現(xiàn)一個(gè)生產(chǎn)者-消費(fèi)者模型生產(chǎn)者線程產(chǎn)生數(shù)據(jù)放入緩沖區(qū)消費(fèi)者線程從緩沖區(qū)取出數(shù)據(jù)進(jìn)行處理要求緩沖區(qū)滿(mǎn)時(shí)生產(chǎn)者等待緩沖區(qū)空時(shí)消費(fèi)者等待。這個(gè)模型本質(zhì)上是操作系統(tǒng)課程中的經(jīng)典同步問(wèn)題但在筆試中用代碼實(shí)現(xiàn)時(shí)很多人會(huì)卡在“條件變量”的使用上。Python中推薦使用threading.Condition來(lái)實(shí)現(xiàn)等待和通知機(jī)制參考實(shí)現(xiàn)如下import threading import time import random class ProducerConsumer: def __init__(self, capacity10): self.buffer [] self.capacity capacity self.cond threading.Condition() def produce(self, item): with self.cond: while len(self.buffer) self.capacity: print(緩沖區(qū)滿(mǎn)生產(chǎn)者等待...) self.cond.wait() self.buffer.append(item) print(f生產(chǎn)了 {item}緩沖區(qū)大小: {len(self.buffer)}) self.cond.notify_all() def consume(self): with self.cond: while not self.buffer: print(緩沖區(qū)空消費(fèi)者等待...) self.cond.wait() item self.buffer.pop(0) print(f消費(fèi)了 {item}緩沖區(qū)大小: {len(self.buffer)}) self.cond.notify_all() return item pc ProducerConsumer(capacity5) def producer_worker(): for i in range(10): pc.produce(i) time.sleep(random.random() * 0.1) def consumer_worker(): for _ in range(10): pc.consume() time.sleep(random.random() * 0.1) t1 threading.Thread(targetproducer_worker) t2 threading.Thread(targetconsumer_worker) t1.start() t2.start() t1.join() t2.join()這里面有兩個(gè)非常容易出錯(cuò)的細(xì)節(jié)第一while len(self.buffer) self.capacity中必須使用while循環(huán)而不是if。原因是當(dāng)多個(gè)生產(chǎn)者線程同時(shí)被喚醒時(shí)可能出現(xiàn)“虛假喚醒”或“競(jìng)爭(zhēng)性喚醒”即使一個(gè)線程被喚醒條件仍可能不滿(mǎn)足。使用while循環(huán)能在每次被喚醒后重新檢查條件確保安全性。第二notify_all()和notify()的選擇。如果只有一個(gè)生產(chǎn)者和一個(gè)消費(fèi)者用notify()就足夠了。但如果存在多個(gè)生產(chǎn)者和多個(gè)消費(fèi)者用notify()可能只會(huì)喚醒同類(lèi)線程導(dǎo)致信號(hào)丟失所以更穩(wěn)妥的做法是使用notify_all()。如果筆試中遇到這類(lèi)題我建議先明確你的設(shè)計(jì)目標(biāo)是單生產(chǎn)者單消費(fèi)者還是多生產(chǎn)者多消費(fèi)者。不同場(chǎng)景下的最佳實(shí)現(xiàn)方式是不同的這也能體現(xiàn)你對(duì)并發(fā)模型的理解深度而不只是背了一個(gè)模板。5.2 線程安全與死鎖預(yù)防的筆試要點(diǎn)平安的并發(fā)編程題通常不會(huì)直接問(wèn)“什么是死鎖”而是會(huì)給你一個(gè)存在死鎖隱患的代碼片段讓你找出問(wèn)題并修復(fù)。這是我整理2020年筆試反饋時(shí)發(fā)現(xiàn)的一個(gè)集中考點(diǎn)。死鎖產(chǎn)生的四個(gè)必要條件是互斥、持有并等待、不可剝奪、循環(huán)等待。筆試中讓你修復(fù)死鎖最常見(jiàn)的解法是破壞“循環(huán)等待”條件即所有線程按相同的順序獲取鎖。舉個(gè)例子如果線程A持有鎖1去申請(qǐng)鎖2而線程B持有鎖2去申請(qǐng)鎖1就會(huì)產(chǎn)生死鎖。修復(fù)方案很直接強(qiáng)制所有線程先申請(qǐng)鎖1再申請(qǐng)鎖2徹底消除循環(huán)等待。在筆試中如果你發(fā)現(xiàn)題目給出的多線程代碼可能存在死鎖風(fēng)險(xiǎn)一定要在答案中明確指出問(wèn)題所在并給出修復(fù)方案這比單純跑通代碼更讓判卷人認(rèn)可。因?yàn)榕芯砣丝吹牟粌H是你寫(xiě)代碼的能力更是你識(shí)別并發(fā)風(fēng)險(xiǎn)的能力。另外在實(shí)際線上筆試環(huán)境中多線程代碼的評(píng)測(cè)結(jié)果可能不是實(shí)時(shí)的、確定的。線程調(diào)度的不確定性導(dǎo)致即使代碼邏輯完全正確輸出順序也未必和預(yù)期完全一致。所以這類(lèi)題目的判分通常是以“關(guān)鍵輸出是否按順序出現(xiàn)”作為依據(jù)而非嚴(yán)格逐字符匹配。我在練習(xí)時(shí)就會(huì)故意運(yùn)行多次確認(rèn)每次運(yùn)行結(jié)果都和預(yù)期一致才敢提交。5.3 并發(fā)場(chǎng)景題在面試中的追問(wèn)方向順帶提一句如果筆試中出現(xiàn)了多線程題面試時(shí)面試官大概率會(huì)圍繞它追問(wèn)。常見(jiàn)的問(wèn)題包括Lock和RLock的區(qū)別是什么什么時(shí)候用RLockCondition的wait()在調(diào)用前為什么要持有鎖如果生產(chǎn)者的速度遠(yuǎn)大于消費(fèi)者的速度怎么優(yōu)化使用queue.Queue和自己實(shí)現(xiàn)的條件變量有什么區(qū)別這些問(wèn)題如果只是背答案容易露餡建議自己在本地多寫(xiě)幾個(gè)并發(fā)小例子把Lock、RLock、Condition、Semaphore、queue.Queue都實(shí)際用一遍觀察它們的行為差異。紙上得來(lái)終覺(jué)淺并發(fā)這塊必須親手跑代碼才能形成真正的理解。6. 從筆試題目看平安的用人標(biāo)準(zhǔn)與復(fù)習(xí)優(yōu)先級(jí)6.1 編程題背后的考察邏輯把平安2020年的編程題放在一起看能清晰地感受到這家公司在技術(shù)校招上的考察標(biāo)準(zhǔn)重視基礎(chǔ)強(qiáng)調(diào)規(guī)范關(guān)注業(yè)務(wù)場(chǎng)景。基礎(chǔ)優(yōu)先沒(méi)有太多偏題怪題大部分題目是LeetCode中檔難度及以下說(shuō)明平安更想招算法基礎(chǔ)扎實(shí)的候選人而不是刷題機(jī)器。規(guī)范至上從字符串壓縮的邊界條件到鏈表反轉(zhuǎn)的指針細(xì)節(jié)再到多線程代碼的死鎖風(fēng)險(xiǎn)處處在考察代碼規(guī)范性和細(xì)節(jié)把控能力。這個(gè)和金融行業(yè)對(duì)代碼質(zhì)量的高要求是吻合的。場(chǎng)景驅(qū)動(dòng)多線程并發(fā)題、數(shù)據(jù)統(tǒng)計(jì)題的出題背景基本都能在平安的業(yè)務(wù)系統(tǒng)中找到對(duì)應(yīng)的影子這說(shuō)明筆試不是單純考算法而是希望候選人能具備將技術(shù)應(yīng)用到實(shí)際業(yè)務(wù)場(chǎng)景的基本素養(yǎng)。我當(dāng)時(shí)準(zhǔn)備校招時(shí)刷了大約200道LeetCode題核心刷了三遍第一遍按類(lèi)型刷建立知識(shí)體系第二遍按難度刷提升手感第三遍只刷高頻題和自己錯(cuò)過(guò)的題鞏固薄弱環(huán)節(jié)。對(duì)于平安這個(gè)級(jí)別的公司這套方法完全夠用。6.2 高效刷題的正確姿勢(shì)說(shuō)到刷題方法我見(jiàn)過(guò)太多無(wú)效刷題的案例最常見(jiàn)的就是“看題五分鐘看答案兩小時(shí)”看的時(shí)候覺(jué)得都懂了合上答案自己寫(xiě)又卡殼。這種刷法對(duì)校招筆試基本沒(méi)有幫助。正確做法是給自己定一個(gè)規(guī)則每道題至少獨(dú)立思考20分鐘如果沒(méi)有思路允許看題解但看完題解后必須合上答案自己從頭到尾把代碼寫(xiě)一遍。寫(xiě)完后再對(duì)比答案看思路是否一致、代碼是否有優(yōu)化空間。通過(guò)這樣的“反饋式刷題”才能把一道題真正內(nèi)化。同時(shí)建議建立自己的錯(cuò)題本記錄每道題的錯(cuò)誤原因。比如“數(shù)組指針越界”“沒(méi)有處理空輸入”“遞歸忘記寫(xiě)終止條件”等??记胺e(cuò)題本比刷新題更高效因?yàn)橹貜?fù)踩同一個(gè)坑才是筆試失分的主要來(lái)源。6.3 關(guān)于2025年P(guān)ython一級(jí)編程題的延伸思考最后聊一個(gè)有意思的題外話。這段時(shí)間在查資料時(shí)看到“python2025.3一級(jí)編程題題目及答案”這個(gè)熱搜詞說(shuō)明Python編程基礎(chǔ)考核的熱度在持續(xù)上升。雖然“一級(jí)編程題”通常面向的是Python初學(xué)者和青少年等級(jí)考試但其中考察的基本功——變量類(lèi)型、條件判斷、循環(huán)、列表操作、字符串方法——恰恰是校招筆試中最核心的底層能力。別覺(jué)得一級(jí)考題簡(jiǎn)單就不屑一顧我見(jiàn)過(guò)不少校招生在筆試?yán)飳?xiě)出if a 1:這種低級(jí)語(yǔ)法錯(cuò)誤。把基礎(chǔ)打牢其實(shí)是最被低估的競(jìng)爭(zhēng)力。如果時(shí)間充裕與其反復(fù)刷高難度題不如把Python基礎(chǔ)語(yǔ)法、常用內(nèi)置方法、標(biāo)準(zhǔn)庫(kù)中最常見(jiàn)的模塊過(guò)一遍。我在2020年筆試時(shí)就因?yàn)樵趇tertools模塊上比較熟悉寫(xiě)一道排列組合題時(shí)直接用itertools.permutations節(jié)省了大量時(shí)間。備考編程題這件事講究的是“以終為始”。你要想清楚筆試考的是什么再倒推自己需要掌握什么。平安這類(lèi)金融科技公司的筆試不是要和ACM選手比“快”而是和業(yè)務(wù)系統(tǒng)的要求比“穩(wěn)”。能寫(xiě)對(duì)、寫(xiě)規(guī)范、寫(xiě)清楚比能寫(xiě)出花來(lái)更重要。從我自己的經(jīng)歷來(lái)看平安科技的2020年校招編程題整體難度適中認(rèn)真準(zhǔn)備一兩個(gè)月完全有能力通過(guò)。希望這篇整理能幫你少走一些彎路。如果有具體題目想深入討論歡迎在評(píng)論區(qū)交流我盡量抽出時(shí)間回復(fù)。