<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Merge Sort on 跟著 Desolve 學程式</title><link>https://desolve.pages.dev/tags/merge-sort/</link><description>Recent content in Merge Sort on 跟著 Desolve 學程式</description><generator>Hugo -- gohugo.io</generator><language>zh-tw</language><lastBuildDate>Wed, 21 Aug 2019 00:00:00 +0800</lastBuildDate><atom:link href="https://desolve.pages.dev/tags/merge-sort/index.xml" rel="self" type="application/rss+xml"/><item><title>從LeetCode學演算法 - 36 Merge Sort (1)</title><link>https://desolve.pages.dev/post/learn-algorithm-from-leetcode-36-merge-sort-1/</link><pubDate>Wed, 21 Aug 2019 00:00:00 +0800</pubDate><guid>https://desolve.pages.dev/post/learn-algorithm-from-leetcode-36-merge-sort-1/</guid><description>&lt;img src="https://desolve.pages.dev/img/default-leetcode.webp" alt="Featured image of post 從LeetCode學演算法 - 36 Merge Sort (1)" /&gt;&lt;p&gt;&lt;strong&gt;0148. Sort List (Medium)&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="question"&gt;Question
&lt;/h2&gt;&lt;p&gt;Sort a linked list in &lt;em&gt;O&lt;/em&gt;(&lt;em&gt;n&lt;/em&gt; log &lt;em&gt;n&lt;/em&gt;) time using constant space complexity.&lt;/p&gt;
&lt;h3 id="example-1"&gt;Example 1
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#8087a2"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#8087a2"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Input: 4-&amp;gt;2-&amp;gt;1-&amp;gt;3
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Output: 1-&amp;gt;2-&amp;gt;3-&amp;gt;4
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="example-2"&gt;Example 2
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#8087a2"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#8087a2"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Input: -1-&amp;gt;5-&amp;gt;3-&amp;gt;4-&amp;gt;0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Output: -1-&amp;gt;0-&amp;gt;3-&amp;gt;4-&amp;gt;5
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="分析解題"&gt;分析/解題
&lt;/h3&gt;&lt;p&gt;試以O(n log n)的時間複雜度來排序一個linked list。&lt;br&gt;
這題其實還有設定一個constant space complexity的條件，&lt;br&gt;
但要達成這個條件的話，要很仔細的使用迭代的方式來做merge sort才行，&lt;br&gt;
這邊主要會講一般的遞迴解(也就是空間複雜度不會是O(1))，&lt;br&gt;
&lt;strong&gt;若對迭代解有興趣，可以參考&lt;/strong&gt; &lt;a class="link" href="https://leetcode.com/problems/sort-list/discuss/172545/Java-iterative-O%281%29-space-O%28nlogn%29-time-solution" target="_blank" rel="noopener"
&gt;&lt;strong&gt;theoneneo&lt;/strong&gt; &lt;/a&gt;&lt;strong&gt;或&lt;/strong&gt; &lt;a class="link" href="https://leetcode.com/problems/sort-list/discuss/268268/Meeting-the-O%281%29-requirement" target="_blank" rel="noopener"
&gt;** sladkey**&lt;/a&gt;** 的解法。**&lt;/p&gt;
&lt;p&gt;關於排序有很多不同的方式，&lt;br&gt;
我們這邊先來講一個相當常見且經典的排序法：&lt;strong&gt;合併排序法(Merge Sort)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;合併排序法的概念是&lt;strong&gt;先拆半再合併&lt;/strong&gt; ，一般在演算法中為了遞迴起見，&lt;br&gt;
會直接把拆半的部分叫&lt;strong&gt;mergesort&lt;/strong&gt; ，而合併的部分就稱為&lt;strong&gt;merge&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;什麼是&lt;strong&gt;拆半&lt;/strong&gt; ？就是每次將整組資料分成兩半，&lt;br&gt;
一路拆分直到只剩下一個，這時候每一個單位而言均為排序好的狀態。&lt;br&gt;
(廢話XD 就一個而已當然是排好的)&lt;/p&gt;
&lt;p&gt;拆分完以後，要進行&lt;strong&gt;合併&lt;/strong&gt; 的動作。&lt;br&gt;
合併就是指將同一層的兩組資料，按大小&lt;strong&gt;由小到大&lt;/strong&gt; 置入。&lt;br&gt;
我們可以參考範例的動畫，一開始由於拆到每組只剩下一個，&lt;br&gt;
可以很輕易的比較誰大誰小，進而每次將較小的放入到合併的陣列中，&lt;br&gt;
所以&lt;strong&gt;每一次合併&lt;/strong&gt; 完以後，我們都可以得到一個**由小排到大的一組資料，&lt;br&gt;
同時下一次我們依舊可以使用兩組各自由小排到大的資料，&lt;br&gt;
使用two pointer的方式就可以依序將其合併。**最後合併回最開始那一層，我們就可以得到一個排序好的陣列了。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://desolve.pages.dev/post/learn-algorithm-from-leetcode-36-merge-sort-1/image-001.gif"
width="1086"
height="541"
srcset="https://desolve.pages.dev/post/learn-algorithm-from-leetcode-36-merge-sort-1/image-001_hu_fadbe020a2a38abb.gif 480w, https://desolve.pages.dev/post/learn-algorithm-from-leetcode-36-merge-sort-1/image-001_hu_96e824f9f9f15194.gif 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="200"
data-flex-basis="481px"
&gt;&lt;/p&gt;
&lt;p&gt;Merge Sort範例&lt;/p&gt;
&lt;p&gt;&lt;img src="https://desolve.pages.dev/post/learn-algorithm-from-leetcode-36-merge-sort-1/image-002.jpeg"
width="800"
height="422"
srcset="https://desolve.pages.dev/post/learn-algorithm-from-leetcode-36-merge-sort-1/image-002_hu_a8691d1f090d4338.jpeg 480w, https://desolve.pages.dev/post/learn-algorithm-from-leetcode-36-merge-sort-1/image-002_hu_8b9b7f3048ec7611.jpeg 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="189"
data-flex-basis="454px"
&gt;&lt;/p&gt;
&lt;p&gt;最後排序完的結果&lt;/p&gt;
&lt;p&gt;這樣子將目標先拆小以後再進行合併的手段，&lt;br&gt;
我們通常稱之為&lt;strong&gt;分治法(divide-and-conquer)&lt;/strong&gt; ，&lt;br&gt;
也就是將大問題拆成小問題解決的一套思路。&lt;br&gt;
由於一定要拆成每組只有單筆資料，每次拆分為一半，&lt;br&gt;
故拆分的時候，會拆成logN層；又每次合併也都需要掃過每筆資料，&lt;br&gt;
總時間複雜度必須再乘上N，所以最終的時間複雜度為&lt;strong&gt;O(NlogN)&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;當中每次合併都需求一個新的位置來作為承接它的狀態，&lt;br&gt;
所以一般來說可使用&lt;strong&gt;O(N)&lt;/strong&gt; 的空間來儲存。&lt;/p&gt;
&lt;p&gt;總體而言，一個mergesort的演算法流程大致如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#8087a2"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#8087a2"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#8087a2"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#8087a2"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#8087a2"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#8087a2"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#8087a2"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#8087a2"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#8087a2"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#8087a2"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#8087a2"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#8087a2"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#8087a2"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#8087a2"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;function mergesort(arr, l, r) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if r &amp;gt; l {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; m = (l+r)/2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mergesort(arr, l, m)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mergesort(arr, m+1, r)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; merge(arr, l, m, r)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;function merge(arr, l, m, r) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 開一個新的陣列tmp，將arr的index l到index r的部分複製過去(包含r)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; i = l, j = m+1, cnt = 0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 比較tmp[i]和tmp[j]，將較小的複製到arr[l+cnt]並遞增cnt跟有被複製的i/j其一
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 重複上一個步驟直到一方沒有可以比較的部分，將剩餘的數字全數依序放入arr中
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;回到我們的問題，&lt;br&gt;
現在要排序的應該是LinkedList，和陣列的唯一差別是，&lt;br&gt;
我們需要一些手段才能取到中間點，這裡同樣使用slow跟fast兩個pointer的方式，讓slow一次走一格，而fast一次走兩格，直到fast到底的時候(遇到NIL)就代表slow應該走到一半了，此時不要忘記將中間的&lt;strong&gt;slow的前一個節點prev跟slow進行斷開&lt;/strong&gt; ，prev的用途就是用來紀錄slow的前一個節點的。&lt;br&gt;
(因為不是陣列，我們是依靠&lt;strong&gt;next是否為NIL&lt;/strong&gt; 這點來判斷節點的分組)&lt;/p&gt;
&lt;p&gt;接著在merge的部分，我們取用了一個n作為dummy node(也就是用來紀錄開頭用的節點)，接著使用iterator(ite)來進行檢查，同樣是檢查哪個比較小，&lt;br&gt;
這時&lt;strong&gt;對於Linked List而言應該是將節點串接在後面&lt;/strong&gt; 即可。(不要忘記串接了某一個以後，該節點也要往下走，相當於陣列中的i跟j其一要遞增。)&lt;/p&gt;
&lt;p&gt;最後同樣，若有一方無法比較，則我們可以直接把剩下的節點接上去。&lt;br&gt;
(ite.next = n1或ite.next = n2)&lt;br&gt;
範例程式碼的部分這邊採用了leetcode使用者jeantimex的解法，&lt;br&gt;
筆者加了一點註解，搭配前面文章的說明，&lt;br&gt;
使用者應該可以較輕鬆地了解整個解題的思路和作法。&lt;/p&gt;
&lt;h3 id="java"&gt;Java
&lt;/h3&gt;&lt;script src="https://gist.github.com/Desolve/e9a0452040f4ca99287e7f96a667baa5.js"&gt;&lt;/script&gt;
&lt;h3 id="python"&gt;Python
&lt;/h3&gt;&lt;script src="https://gist.github.com/Desolve/ec297770b4aece9582dcdcd5bc10005c.js"&gt;&lt;/script&gt;
&lt;h2 id="面試實際可能會遇到的問題"&gt;面試實際可能會遇到的問題
&lt;/h2&gt;&lt;p&gt;「時間/空間複雜度？」&lt;br&gt;
(O(NlogN), O(logN)&lt;br&gt;
將一個串列不斷拆成二等份，直到剩下單一節點，&lt;br&gt;
相當於在求N可以被二分拆幾次，乘上最終會有N個單節點再次合併，&lt;br&gt;
故總體時間複雜度為O(NlogN)。) &lt;br&gt;
(註:這只是很粗糙的說法，有興趣可以翻閱演算法書籍，會有較嚴謹的證明)&lt;br&gt;
(對於每一次的拆分搜尋都需要用O(1)的記憶體來儲存這層的stack，&lt;br&gt;
理想狀況下會有logN層，故平均空間複雜度為O(logN)，&lt;br&gt;
最差的狀況則為O(N))&lt;/p&gt;
&lt;p&gt;「還有一些常見的排序，你能說出它們的時間複雜度嗎？」&lt;br&gt;
(selection/insertion/bubble等均為O(N²)，quick/merge均為O(NlogN)，&lt;br&gt;
需留意quicksort的worst case是O(N²))&lt;/p&gt;
&lt;h2 id="相似及延伸"&gt;相似及延伸
&lt;/h2&gt;&lt;p&gt;1.&lt;a class="link" href="https://medium.com/@desolution/%E5%BE%9Eleetcode%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-4-6a631eb50da3?source=your_stories_page---------------------------" target="_blank" rel="noopener"
&gt;&lt;strong&gt;0021. Merge Two Sorted Lists (Easy)&lt;/strong&gt; &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Special Thanks suggestions/corrections from viewers:&lt;br&gt;
(歡迎提供意見協助更正歐~)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;從LeetCode學演算法，我們下次見囉，掰~&lt;/p&gt;</description></item></channel></rss>