1. 程式人生 > >開啟演算法之路,還原題目,用debug除錯搞懂每一道題

開啟演算法之路,還原題目,用debug除錯搞懂每一道題

#### 文章簡述 大家好,本篇是個人的第 3 篇文章。 承接第一篇文章《手寫單鏈表基礎之增,刪,查!附贈一道連結串列題》,在第一篇文章中提過,在刷演算法題之前先將基礎知識過一遍,這樣對後面的做演算法題是很有幫助的。 在本次的文章中,按照個人的刷題計劃,會分享關於連結串列的 3 道簡單級別的演算法題(可是依然感覺不簡單) 但是不要緊,從本篇文章開始分享的演算法題個人都會把關於這道題的全部程式碼寫出來,`並用debug的形式`,分解每一步來整理出來。 通過還原題目場景,用 debug 除錯的方式去分析,印象更加深刻些。 本篇文章中共有 3 道題目。 #### 一,合併兩個有序連結串列 ##### 1.1 題目分析 看到這道題的時候,第一反應就是先將兩個連結串列合併,然後再排序。嗯。。。不用想,絕對的暴力寫法。 或者是迴圈兩個連結串列,然後兩兩相比較,就像: ```java for(){ for(){ if(){} } } ``` 好吧,其實這道題精華在於可以使用`遞迴`,這個。。。來個草圖簡單描述下。 第一步: 兩個連結串列的首節點進行比較
兩個節點相等,則使 L2 連結串列【1】,和 L1 連結串列的【2】進行比較 **注意:** `L1節點【1】和L2節點【1】比較完成後,需要修改1.next指標,以指向它的下個節點。` 第二步: 現在我們獲取到了 L2 連結串列【1】,那它的 next 指向誰?也就是 L2 連結串列【1】去和 L1 連結串列的【2】進行比較。 比較完成後,L2 連結串列【1】的 next 就指向了 L1 連結串列【2】,接著以此類推。 L2 連結串列【3】去和 L1 連結串列【4】比較。 最後 L1 連結串列【4】和 L2 連結串列【4】比較。 全部比較完成後,整個連結串列就已經排序完成了。
`遞迴的方式就在於,兩兩節點進行比較,當有一個連結串列為null時,表示其中一個連結串列已經遍歷完成,那就需要終止遞迴,並將比較結果進行返回。` 可能只是單純的畫圖並不好理解,下面用程式碼 debug 的方式去分析,還請耐心往下看。 ##### 1.2 程式碼分析 按照題意需要先建立 2 個單鏈表,具體的建立方式可以參考本人的第一篇文章《手寫單鏈表基礎之增,刪,查!附贈一道連結串列題》。不多說,先初始化節點物件。 ```java class ListNode { /** * 資料域 */ int val; /** * 下一個節點指標 */ ListNode next; ListNode() { } ListNode(int val) { this.val = val; } ListNode(int val, ListNode next) { this.val = val; this.next = next; } @Override public String toString() { return "ListNode{" + "val=" + val + '}'; } } ``` 定義新增連結串列方法。 ```java public ListNode add(ListNode node) { // 1,定義輔助指標 ListNode temp = nodeHead; // 2,首先判斷當前節點是否為最後節點 if (null == temp.next) { // 當前節點為最後節點 temp.next = node; return nodeHead; } // 3,迴圈遍歷節點,如果當前節點的下個節點不為空,表示還有後續節點 while (null != temp.next) { // 否則將指標後移 temp = temp.next; } // 4,遍歷結束,將最後節點的指標指向新新增的節點 temp.next = node; return nodeHead.next; } ``` 接著建立 2 個連結串列。 ```java /** * 定義L1連結串列 */ ListNode l11 = new ListNode(1); ListNode l12 = new ListNode(2); ListNode l13 = new ListNode(4); MergeLinkedList l1 = new MergeLinkedList(); l1.add(l11); l1.add(l12); /** * 返回新增完的L1連結串列 */ ListNode add1 = l1.add(l13); /** * 定義L2連結串列 */ ListNode l21 = new ListNode(1); ListNode l22 = new ListNode(3); ListNode l23 = new ListNode(4); MergeLinkedList l2 = new MergeLinkedList(); l2.add(l21); l2.add(l22); /** * 返回L2連結串列 */ ListNode add2 = l2.add(l23); ``` 我們先把上述圖中使用遞迴的程式碼貼出來。 ```java public ListNode mergeTwoLists(ListNode l1, ListNode l2) { /** * 如果L1連結串列為null,終止遞迴 */ if (l1 == null) { return l2; } if (l2 == null) { return l1; } /** * 按照圖中的描述,兩兩比較連結串列的節點 */ if (l1.val <= l2.val) { /** * L1的節點比L2的小,按照圖中就是需要比較L1連結串列的下個節點 * l1.next 就是指當比較出節點大小後,需要修改指標的指引,將整個連結串列全部串聯起來 */ l1.next = mergeTwoLists(l1.next, l2); return l1; } else { /** * 同理,與上個if判斷一樣 */ l2.next = mergeTwoLists(l1, l2.next); return l2; } } } ``` ##### 1.3 debug 除錯 ###### 1.3.1 L1 連結串列已經建立完成,同理 L2 也被建立完成。
###### 1.3.2 比較兩個連結串列的首節點 **注意:** `l1.next是指向遞迴方法的,也就是上圖中我們描述的l1連結串列【1】指向了l2連結串列【1】,但是L2連結串列【1】又指向誰?開始進入遞迴` ###### 1.3.3 如上圖,開始比較 L2 連結串列【1】與 L1 連結串列的【2】 ###### 1.3.4 比較 L1 連結串列【2】與 L2 連結串列【3】 ###### 1.3.5 後面操作是一樣的,下面就直接展示最後兩個節點比較 這裡已經到最後兩個節點的比較。 這個時候 L1 連結串列先遍歷完成,需要終止遞迴,返回 L2 連結串列。為什麼返回 L2 連結串列?直接看圖。 因為最後一步比較的是 L1 連結串列【4】和 L2 連結串列【4】,也就是說 L2 連結串列【4】是最後的節點,如果返回 L1 連結串列,那 L2 連結串列【4】就會被丟棄,可參考上面圖解的最後一張。 **重點來了!!!** **重點來了!!!** **重點來了!!!** L1 連結串列已經遍歷完成,開始觸發遞迴將比較的結果逐次返回。 這是不是我們最後 L1 連結串列【4】和 L2 連結串列【4】比較的那一步,是不是很明顯,l1.next 指向了 l1 的節點【4】,而 L1 節點也就是它最後的節點【4】,和我們那上面圖解中最後的結論一樣。 再接著執行下一步返回。 **L2 連結串列的【3】指向了 L1 連結串列的【4】** 同理,按照之前遞迴的結果以此返回就可以了,那我們來看下最終的排序結果。 #### 二,刪除排序連結串列中的重複元素 ##### 2.1 題目分析 初次看這道題好像挺簡單的,這不就是個人之前寫的第一篇文章裡面,刪除連結串列節點嗎! 仔細審題其實這道題要更簡單些,因為題中已說明是一個排序連結串列,因此我們只需要將當前節點與下一個節點進行比較,如果相等則直接修改 next 指標即可。 ##### 2.1 程式碼分析 同樣是連結串列的定義,與上面第一題中的建立是一樣的,只不過我們是需要再重新建立一個單鏈表。 ```java ListNode l1 = new ListNode(1); ListNode l2 = new ListNode(1); ListNode l3 = new ListNode(3); ListNode l4 = new ListNode(4); ListNode l5 = new ListNode(4); ListNode l6 = new ListNode(5); NodeFun nodeFun = new NodeFun(); nodeFun.add(l1); nodeFun.add(l2); nodeFun.add(l3); nodeFun.add(l4); nodeFun.add(l5); ListNode listNode = nodeFun.add(l6); ``` 建立完成後,接著看去重複的程式碼。 ```java public ListNode deleteDuplicates(ListNode head) { /** * 定義輔助指標 */ ListNode temp = head; /** * 判斷當前節點和下一個節點不能為空,因為是需要將當前節點和下一個節點進行比較的 */ while (temp != null && temp.next != null) { /** * 如果節點值相同 */ if (temp.val == temp.next.val) { /** * 表示當前節點與下一個節點的值相同,則移動指標 */ temp.next = temp.next.next; } else { /** * 必須移動指標,否則會產生死迴圈 */ temp = temp.next; } } return head; } } ``` ##### 2.2 debug 除錯 ###### 2.2.1 按照初始化的連結串列,應該是首節點【1】和第二個節點【1】進行比較。 不用說兩個節點是相等的,那下一步進入 if 判斷,就是修改指標的指向。 此時第二個節點【1】已經沒有被 next 指引了,就會被 GC 回收掉。 ###### 2.2.2 下一步就是節點【1】和節點【3】進行比較 兩個節點不相等,進入 else 將輔助指標移動到下個節點。 那麼剩下的節點判斷也都是一樣的,我們最後看下列印的結果。 #### 三,環形連結串列 ##### 3.1 題目分析 如果這個連結串列裡面有環,其中一個節點必然是被指標指引了一次或者多次(如果有多個環的話)。因此個人當時簡單的做法就是遍歷連結串列,把遍歷過的節點物件儲存到 HashSet 中,每遍歷下一個節點時去 HashSet 中比對,存在就表示有環。 而這道題沒有設定過多的要求,只要有環返回 boolean 就好。 還有一種巧妙的寫法,使用快慢指標的思想。 這種方式大致意思就是說,快慢指標比作龜兔賽跑,兔子跑的快,如果存在環那麼兔子就會比烏龜先跑進環中。那麼它們就會在某個節點上相遇,相遇了也就說明連結串列是有環的。 **那麼,你們問題是不是來了?這不公平啊,【兔子】本來就比【烏龜】跑的快,那咋兔子還先跑了。** 試想,如果它倆都在一個節點上跑,那它們從開始不就是相遇了,因為我們我們是設定如果在一個節點上相遇,表示連結串列是有環的。所以,這不是“不打自招“了! **比賽開始,這【兔子大哥】有點猛啊,一下跑兩個節點。** **果然,有情人終成眷屬,它們相遇了。** ##### 3.2 程式碼分析 這次建立連結串列的時候,就不能單純是個單鏈表了,還得加個環。 ```java ListNode l1 = new ListNode(3); ListNode l2 = new ListNode(2); ListNode l3 = new ListNode(0); ListNode l4 = new ListNode(-4); /** * 給主角加個環 */ l4.next = l2; NodeFun nodeFun = new NodeFun(); nodeFun.add(l1); nodeFun.add(l2); nodeFun.add(l3); ListNode listNode = nodeFun.add(l4); ``` 那就一起來找環吧。 ```java public boolean hasCycle(ListNode head) { ListNode temp = head; if(null == head){ // 為空表示沒有環 return false; } // 1,set集合儲存遍歷過的節點,如果新的節點已經在set中,表示存在環 // 2,使用快慢指標的思想 // 定義慢指標 ListNode slow = head; // 定義快指標 ListNode fast = head.next; // 迴圈,只要2個指標不重合,就一直迴圈 while(slow != fast){ // 如果2個指標都到達尾節點,表示沒有環 if(fast == null || fast.next == null){ return false; } // 否則就移動指標 slow = slow.next; fast = fast.next.next; } return true; } ``` ##### 3.3 debug 除錯 所以,尷尬的事情來了,這玩意 debug 不了啊。如果存在環,那麼 while 迴圈是不會進來的。 那就直接看下結果吧。 如果把環去掉就是? 那還用猜?沒有光環了肯定。。。 #### 四,總結 本次分享總結的題目都是簡單的題,因為也是按照個人制定的刷題計劃開始的,後面我會把個人的刷題計劃整理分享出來。 **關於文章中完整的程式碼,關注公眾號回覆【獲取原始碼】,目前所有程式碼都是一個工程,也是方便後面的程式碼更新,可以自己親自除錯。** 期待與大家共勉! #### 最後,求關注 原創不易,每一篇都是用心在寫。如果對您有幫助,就請一鍵三連(**關注**,點贊,再轉發) 我是楊小鑫,堅持寫作,分享更多有意義的文章。 感謝您的閱讀,期待與您相識!

相關推薦

開啟演算法還原題目debug除錯一道

#### 文章簡述 大家好,本篇是個人的第 3 篇文章。 承接第一篇文章《手寫單鏈表基礎之增,刪,查!附贈一道連結串列題》,在第一篇文章中提過,在刷演算法題之前先將基礎知識過一遍,這樣對後面的做演算法題是很有幫助的。 在本次的文章中,按照個人的刷題計劃,會分享關於連結串列的 3 道簡單級別的演算法題(可

連結串列演算法還原題目debug除錯一道

#### 文章簡述 大家好,本篇是個人的第4篇文章。 承接第3篇文章《開啟演算法之路,還原題目,用debug除錯搞懂每一道題》,本篇文章繼續分享關於連結串列的演算法題目。 本篇文章共有5道題目 #### 一,反轉連結串列(經典題目) ##### 1.1.1 題目分析 反轉連結串列是經典

簡報 | 日本虛擬貨幣交易所協會獲得監管許可開啟自治

行情概覽 51BB8財經 對接全網大資料行情,收錄幣種4,072個,截至10月25日17時00分,24h交易量108億美元,上漲幣種2,593個,下跌幣種1,479個。 24h成交量交易所TOP10 熱門幣種 BTC: BTC昨日震盪下行,現位於6,55

Java學習:不走彎路就是捷徑

下載地址 下載 何事 系統 也有 包括 軟件公司 項目管理師 應用 1.如何學習程序設計? Java是一種平臺,也是一種程序設計語言,如何學好程序設計不僅僅適用於Java,對C++等其他程序設計語言也一樣管用。有編程高手認為,JAVA也好C也好沒什麽分別,拿來就用。為什麽他

Python學習6?函數遞歸內置函數

erro memory 子程序 none 種類 lan 萬年 字典 得到 一python中的函數 函數是邏輯結構化和過程化的一種編程方法。 python中函數定義方法: def test(x): "The function definitions" x

卅川的狀態機(創作中不定時上傳)

rom 不同的 大學 核心 追溯 選擇 有限狀態機 span 任務 川的第一篇幹貨,將從講述FSM(有限狀態機)開始。 川第一次接觸狀態機這種東西,還得追溯到剛到暢遊工作,破解了別的遊戲的代碼(遊戲程序就是這麽沒節操和底線,嗯!)才知道有這麽個東西的。雖然大學學習過相

軟考--從生活著手看PV怎樣操作

是我 信號 技術 生活 復雜 問題 工具 否則 是否 PV操作。是軟考其中一個非常重要的考點,一聽到這個名詞,頓時趕腳高大上有麽有,在軟考的歷年試題中,也不乏PV操作的身影,老師也對PV操作進行了一次講課,那時年少。聽得稀裏糊塗,也不是非常理解,在小編

PS圖層混合演算法三(濾色 疊加 柔光 強光)

濾色模式: 作用結果和正片疊底剛好相反,它是將兩個顏色的互補色的畫素值相乘,然後除以255得到的最終色的畫素值。通常執行濾色模式後的顏色都較淺。任何顏色和黑色執行濾色,原色不受影響;任何顏色和白色執行濾色得到的是白色;而與其他顏色執行濾色會產生漂白的效果。  Screen 濾色 C=1-(1-

保研------塵埃落定(復旦同濟北郵)

自我介紹:本科南京郵電大學貝爾英才學院 保研院校:碩士北京郵電大學網路研究院 不知不覺已經大四了, 就在今天的下午我確認了北郵的擬錄取通知,其實我的內心還是有些失落和不甘的。看到同學有去清華,北大、浙大、同濟,覺得很羨慕。我也總結一下自己保研的一些遺憾和後悔吧。 首先:保研之前我就

前端:sql語句表中隨機獲取一條記錄(資料)。(或者獲取隨機獲取多條(記錄)資料)

<!--表中獲取隨機一條title 耗時0.01s id==隨機欄位,最好為表id--> SELECT * FROM `tableName` AS t1 JOIN (SELECT ROUND(RAND() * ((SELECT MAX(id) FROM `ta

英特爾的顯卡1——獨顯我也曾擁有!

技術分享 tps 封裝 intel 結果 more 目標 處理 自主 之前看到一篇很好的文章,出自《電腦愛好者》2017年12月份刊。篇幅不短,感覺應該是這一期壓軸文章,封面也有介紹~通讀一遍下來感覺內容豐富,言辭不偏不倚。能見到這樣的文章實屬難得。今天有時間整理一下,分為

【58沈劍 架構師】資料庫索引到底是什麼做的?

問題1. 資料庫為什麼要設計索引?   圖書館存了1000W本圖書,要從中找到《架構師之路》,一本本查,要查到什麼時候去? 於是,圖書管理員設計了一套規則: (1)一樓放歷史類,二樓放文學類,三樓放IT類… (2)IT類,又分軟體類,硬體類… (3)軟體類,又按照書名音序排序… 以便快

【58沈劍 架構師】資料庫索引到底是什麼做的?

問題1. 資料庫為什麼要設計索引?   圖書館存了1000W本圖書,要從中找到《架構師之路》,一本本查,要查到什麼時候去? 於是,圖書管理員設計了一套規則: (1)一樓放歷史類,二樓放文學類,三樓放IT類… (2)IT類,又分軟體類,硬體類… (3)軟體類,又按照書

opengl學習三十九文字渲染

當你在圖形計算領域冒險到了一定階段以後你可能會想使用OpenGL來繪製文字。然而,可能與你想象的並不一樣,使用像OpenGL這樣的底層庫來把文字渲染到螢幕上並不是一件簡單的事情。如果你只需要繪製128種不同的字元(Character),那麼事情可能會簡單一些。

opengl學習三十二視差貼圖

本節暫未進行完全的重寫,錯誤可能會很多。如果可能的話,請對照原文進行閱讀。如果有報告本節的錯誤,將會延遲至重寫之後進行處理。 視差貼圖(Parallax Mapping)技術和法線貼圖差不多,但它有著不同的原則。和法線貼圖一樣視差貼圖能夠極大提升表面細節,使之

opengl學習三十五延遲著色法

Note 本節暫未進行完全的重寫,錯誤可能會很多。如果可能的話,請對照原文進行閱讀。如果有報告本節的錯誤,將會延遲至重寫之後進行處理。 我們現在一直使用的光照方式叫做正向渲染(Forward Rendering)或者正向著色法(Forward Shading)

opengl學習三十八光照

Note 本節暫未進行完全的重寫,錯誤可能會很多。如果可能的話,請對照原文進行閱讀。如果有報告本節的錯誤,將會延遲至重寫之後進行處理。 譯者注: 閱讀本節請熟悉上一節提到的幾個名詞: 輻射通量(Radiant flux) 輻射率(Radiance) 輻照度(

opengl學習三十四泛光

Note 本節暫未進行完全的重寫,錯誤可能會很多。如果可能的話,請對照原文進行閱讀。如果有報告本節的錯誤,將會延遲至重寫之後進行處理。 明亮的光源和區域經常很難向觀察者表達出來,因為監視器的亮度範圍是有限的。一種區分明亮光源的方式是使它們在監視器上發出光芒,光

opengl學習三十七理論

Note 本節暫未進行完全的重寫,錯誤可能會很多。如果可能的話,請對照原文進行閱讀。如果有報告本節的錯誤,將會延遲至重寫之後進行處理。 PBR,或者用更通俗一些的稱呼是指基於物理的渲染(Physically Based Rendering),它指的是一些在不同

opengl學習三十六SSAO

Note 本節暫未進行完全的重寫,錯誤可能會很多。如果可能的話,請對照原文進行閱讀。如果有報告本節的錯誤,將會延遲至重寫之後進行處理。 我們已經在前面的基礎教程中簡單介紹到了這部分內容:環境光照(Ambient Lighting)。環境光照是我們加入場景總體光