寫在前面
今天筆者想來和大家討論一下,刷演算法題的一些心得
說到演算法題想必很多同學都會有許許多多的討論,有的同學認為刷演算法題是必修課,有的同學認為演算法不實用,工作中用不到。
那麼筆者的態度是什麼,以前其實已經說過了,還是那句話:必須刷
至於為什麼,後面會解釋,並且筆者還會和大家討論如何把題目刷好
實質分析
拋開事實談邏輯那叫耍流氓,因此筆者就先冒犯一下,把大家當做傻瓜,拿一道題來做個演示,我們在刷題過程中到底在做些什麼
將一個給定字串 s 根據給定的行數 numRows ,以從上往下、從左到右進行 Z 字形排列。
比如輸入字串為 "PAYPALISHIRING" 行數為 3 時,排列如下:
P A H N
A P L S I I G
Y I R
之後,你的輸出需要從左往右逐行讀取,產生出一個新的字串,比如:"PAHNAPLSIIGYIR"。
請你實現這個將字串進行指定行數變換的函式:
string convert(string s, int numRows);
示例 1:
輸入:s = "PAYPALISHIRING", numRows = 3
輸出:"PAHNAPLSIIGYIR"
示例 2:
輸入:s = "PAYPALISHIRING", numRows = 4
輸出:"PINALSIGYAHRPI"
解釋:
P I N
A L S I G
Y A H R
P I
示例 3:
輸入:s = "A", numRows = 1
輸出:"A"
提示:
1 <= s.length <= 1000
s 由英文字母(小寫和大寫)、',' 和 '.' 組成
1 <= numRows <= 1000
第一步:審題
同學們在思考這道題的時候,腦海裡是不是會出現以下內容
這道題可以透過模擬Z字形排列的過程來解決。我們可以使用一個二維陣列來表示Z字形排列的結果,然後按照從上到下、從左到右的順序將字串中的字元填入二維陣列中。最後,按照從左到右、從上到下的順序讀取二維陣列中的字元,得到最終的結果字串。
大家看看題目這個東西有半點技術嘛?很明顯沒有,那麼在整個軟體工程中,唯一重要,但是文字字裡行間不會涉及到技術的東西是啥,沒錯就是需求,同學們發現沒有,大家審題的時候是不是相當於我們在做需求分析。我們到底要具體實現一些什麼東西,是不是這個時候都要分析出來
第二步:分析
然後我們接下來刷題的時候會怎麼做?是不是同學們腦海中會出現以下內容
- 如果 numRows 為 1,直接返回原字串 s。
- 建立一個 numRows 行的二維陣列,用於儲存 Z 字形排列的結果。
- 初始化當前行和當前列為 0,表示當前要填入的位置。
- 遍歷字串 s 中的每個字元:
- 將當前字元填入二維陣列的當前位置。
- 如果當前行小於 numRows-1,表示還可以向下移動一行,將當前行加 1。
- 否則,表示已經到達 Z 字形的底部,需要向上移動一行,將當前行減 1,同時當前列加 1。
- 按照從左到右、從上到下的順序讀取二維陣列中的字元,得到最終的結果字串。
時間複雜度分析:遍歷字串 s 的時間複雜度是 O(n),其中 n 是字串的長度。最後按照從左到右、從上到下的順序讀取二維陣列中的字元的時間複雜度也是 O(n)。因此,總的時間複雜度是 O(n)。
空間複雜度分析:建立二維陣列的空間複雜度是 O(numRows * n),其中 numRows 是行數,n 是字串的長度。最後返回的結果字串的空間複雜度是 O(n)。因此,總的空間複雜度是 O(numRows * n)。
同學們發現沒有,我們在敲程式碼前,需要幹什麼,對,到底要怎麼實現需求,我們腦海中需要有一個大致的步驟,先做什麼後做什麼,需要很清楚,這像不像軟體工程中的設計階段
第三步:編碼
這一步我想是大家最熟悉的階段,筆者就不再過多闡述了,但是筆者這裡還想再次強調一下,包括之前不止一次強調過,敲程式碼這項工作大約只佔整個軟體工程中的兩到三成,現在我想大家應該又能夠有點感受了吧,很多同學為什麼覺得敲程式碼痛苦地不得了,因為同學,你把很多前面應該做的事,全部都省略了,編碼階段負責給你前面的偷懶來擦屁股,你當然會覺得痛苦萬分。筆者還是那句話,你只會編碼,你不叫工程師,你叫程式設計師。
編碼階段要做的事情只有一個,就是利用你腦中的程式設計知識,巧妙地實現你前面的分析和設計,這個階段要想做好,你要做的事情只有一件,就只是對你在用的程式碼很熟悉,就可以了
事實上分析和設計都是需要單獨練的,很多同學從來沒有系統練過,只是一味地敲程式碼,這樣不成體系的混亂學習,那怎麼會有明顯的長進呢。
第四步:除錯
往往做好題目以後,會出現各種各樣的問題,除了AC會有很多讓我們心態爆炸的結果,最典型的就是以下這些
- CE(Compile Error):編譯錯誤。
- 你的程式存在語法錯誤(C / C++ 最常見的是缺少分號、缺少括號、使用了中文標點符號或者函式呼叫錯誤等等)或者OJ系統不支援的寫法(較少見)。
- 此時應當仔細檢查程式碼在本機能否透過編譯,改正後再次提交。
- PE(Presentation Error):輸出格式錯誤。
- 你的程式幾乎能AC了,但是和標準輸出資料有點細微的差距(大小寫,空格數量,換行數量之類的)。
- 此時應當仔細觀察題目給出的輸出樣例,確認格式無誤(選中資料貼上到編輯器最為穩妥)。
- WA(Wrong Answer):答案錯誤。
- 你程式輸出的結果有錯誤,與期望輸出不匹配(也有可能是因為缺少了必要的換行和空格)。
- 請檢查你的程式是否出現了致命的邏輯錯誤,當然有的時候是因為手滑。
- TLE(Time Limit Exceed):超出執行時間限制。
- 你的程式可能因為時間效率不高或者出現了死迴圈,所以未能在規定的時限內執行結束。
- MLE(Memory Limit Exceed):超出執行記憶體限制。
- 你的程式佔用的記憶體超過了規定值,可能是因為使用了過大的陣列,也可能是沒有做到記憶體釋放(較少見)
這個時候我們一般看到這樣的結果我們就需要幹什麼?沒錯,修改程式碼中的細節,甚至是整個思路錯了,要重新設計程式碼,有時候看測試樣例的時候,我們甚至都會罵街,出題人為什麼會給出這麼奇怪的測試案例
已經工作的同學們,或者是已經從老師口中對工作有些許瞭解的同學,試著想想看開發人員和測試人員相愛相殺的畫面,再看看現在這個畫面,是不是很像,沒錯,我們一步步邁向AC的過程是在幹什麼,相當於軟體工程中的運維測試。
小結
所以,同學們發現了沒有,我們在做演算法題的時候,一道中等難度的題目,大約十幾分鍾到半個小時的過程,我們已經進行了一個簡易的軟體工程過程,這也就解釋了為啥很多廠,對應屆生的似乎演算法佔比很高,現在大家應該明白了吧,因為應屆生的專案經驗比較少,但是他演算法題刷得多,一定程度上就彌補了他對整個工程認知不足的問題。至少軟體工程,在他的腦海裡形成了一個雛形。公司只要稍加培養,很快就能上手幹活。
另外也解釋了有些同學強調實習,強調專案經驗,實際上也是一樣的道理,他已經有了足夠多的專案經驗,從技術到工程流程他都已經比較熟悉,自然不需要演算法來彌補他的缺失。
演算法本質
肯定有同學要問了,你講到現在似乎沒扯到演算法的內容,同學們,不要急。
如果現在沒有演算法這個概念,只是告訴你刷題,對於比較細心的同學,你們會怎麼做?沒錯,就是把大量的題目放在一起總結規律,把很多類似的問題放到一起總結共同的方法,甚至寫出共同的程式碼來,以後再遇到,就不用直接分析了,而是直接套用你手上已經總結好的東西,從而大幅加快速度。
那麼,前輩們是很聰明的,他們已經幫你都總結好了,同學們平時學的演算法和資料結構,就是在不斷地解決各種問題中總結出來的成果。
所以,同學們現在明白了嗎,你學的演算法和資料結構,就等同於是站在巨人的肩膀上,省時省力。但是大家還記不記得演算法的概念是什麼,演算法是一種用於解決問題或執行特定任務的有序步驟的描述。所以,這個東西是沒有止境的,同學們還要不斷地去總結。
切換語言
很多同學可能都在頭疼學幾門語言的事情,有些同學學了語言沒有地方練習,那麼筆者的建議是什麼,在前面筆者也已經提到了,編碼階段要做的事情只有一個,就是利用你腦中的程式設計知識,巧妙地實現你前面的分析和設計,這個階段要想做好,你要做的事情只有一件,就只是對你在用的程式碼很熟悉,就可以了。
所以,筆者給大家的建議是什麼,針對你已經分析過的題目,你換一個語言再試著去實現一遍,當然編碼和設計是相輔相成的,不同的語言可能在設計上會略有不同,但是如果都是物件導向的語言,基本上大體不會差到哪裡去。
你只要解決,這個設計在你現在的語言中該怎麼實現的問題就可以了。
以後的路
如果看到這篇文章的同學已經工作了四五年時間,那麼這個時候還要不要刷題呢,筆者的回答是,還得刷,筆者的理由有以下這些
【1】按照上面已經提到了,已經刷過的題目針對切換語言是很重要的,語言流不流行說實話我們說了不算,未來有啥新的語言,我們必須儘快掌握,否則很有可能就被淘汰了
【2】練習重要環節,刷題這件事情,某種程度上也是軟體工程,麻雀雖小五臟俱全。公司專案,拿到稱心專案的主動權不在我們手上,個人專案,提出好需求的難度也很高,而刷題這些幾千幾萬條需求,不拿來練習實在是太浪費的
附註
那麼今天就和大家聊到這裡,希望筆者可以給大家帶來一些幫助,筆者接下來會更加努力的工作,給大家帶來更多的經驗分享,希望同學們工作順利,早日升職加薪、當上總經理、出任CEO、迎娶白富美、走上人生巔峰,想想是不是還有點小激動呢