UNPKG

ds-algo-study

Version:

Just experimenting with publishing a package

607 lines (589 loc) 516 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link href='http://fonts.googleapis.com/css?family=Droid+Sans:700' rel='stylesheet'> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous"> <link rel="stylesheet" href="./prism.css"> <script async defer src="./prism.js"></script> <link rel="stylesheet" href="./style.css"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous"> <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script> <script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script> <link rel="stylesheet" href="./prism.css"> <script async defer src="./prism.js"></script> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> </style> </head> <body> <div class="container" id="body"> <h1 id="clarify-the-concept-of-recursion">Clarify the concept of recursion</h1> <p>What's the difference and connections between recursion, divide-and-conquer algorithm, dynamic programming, and greedy algorithm? If you haven't made it clear. Doesn't matter! I would give you a brief introduction to kick off this section.</p> <p>Recursion is a programming technique. It's a way of thinking about solving problems. There're two algorithmic ideas to solve specific problems: divide-and-conquer algorithm and dynamic programming. They're largely based on recursive thinking (although the final version of dynamic programming is rarely recursive, the problem-solving idea is still inseparable from recursion). There's also an algorithmic idea called greedy algorithm which can efficiently solve some more special problems. And it's a subset of dynamic programming algorithms.</p> <p>The divide-and-conquer algorithm will be explained in this section. Taking the most classic merge sort as an example, it continuously divides the unsorted array into smaller sub-problems. This is the origin of the word <strong>divide and conquer</strong>. Obviously, the sub-problems decomposed by the ranking problem are non-repeating. If some of the sub-problems after decomposition are duplicated (the nature of overlapping sub-problems), then the dynamic programming algorithm is used to solve them! </p> <h2 id="recursion-in-detail">Recursion in detail</h2> <p>Before introducing divide and conquer algorithm, we must first understand the concept of recursion.</p> <p>The basic idea of recursion is that a function calls itself directly or indirectly, which transforms the solution of the original problem into many smaller sub-problems of the same nature. All we need is to focus on how to divide the original problem into qualified sub-problems, rather than study how this sub-problem is solved. The difference between recursion and enumeration is that enumeration divides the problem horizontally and then solves the sub-problems one by one, but recursion divides the problem vertically and then solves the sub-problems hierarchily.</p> <p>The following illustrates my understanding of recursion. <strong>If you don't want to read, please just remember how to answer these questions:</strong></p> <ol type="1"> <li>How to sort a bunch of numbers? Answer: Divided into two halves, first align the left half, then the right half, and finally merge. As for how to arrange the left and right half, please read this sentence again. </li> <li>How many hairs does Monkey King have? Answer: One plus the rest.</li> <li>How old are you this year? Answer: One year plus my age of last year, I was born in 1999.</li> </ol> <p>Two of the most important characteristics of recursive code: <strong>end conditions and self-invocation</strong>. Self-invocation is aimed at solving sub-problems, and the end condition defines the answer to the simplest sub-problem.</p> <div class="sourceCode" id="cb1"> <pre data-filter-output="(out)" class="sourceCode cpp"><code class="sourceCode cpp"><a class="sourceLine" id="cb1-1" title="1"><span class="lang-js dt">int</span> func(How old are you <span class="lang-js kw">this</span> year) {</a> <a class="sourceLine" id="cb1-2" title="2"></a> <a class="sourceLine" id="cb1-3" title="3"> <span class="lang-js co">// simplest sub-problem, end condition</span></a> <a class="sourceLine" id="cb1-4" title="4"> <span class="lang-js cf">if</span> (<span class="lang-js kw">this</span> year equals <span class="lang-js dv">1999</span>) <span class="lang-js cf">return</span> my age <span class="lang-js dv">0</span>;</a> <a class="sourceLine" id="cb1-5" title="5"> <span class="lang-js co">// self-calling to decompose problem</span></a> <a class="sourceLine" id="cb1-6" title="6"> <span class="lang-js cf">return</span> func(How old are you last year) + <span class="lang-js dv">1</span>; </a> <a class="sourceLine" id="cb1-7" title="7"></a> <a class="sourceLine" id="cb1-8" title="8">}</a></code></pre> </div> <p>Actually think about it, <strong>what is the most successful application of recursion? I think it's mathematical induction</strong>. Most of us learned mathematical induction in high school. The usage scenario is probably: we can't figure out a summation formula, but we tried a few small numbers which seemed containing a kinda law, and then we compiled a formula. We ourselves think it shall be the correct answer. However, mathematics is very rigorous. Even if you've tried 10,000 cases which are correct, can you guarantee the 10001th correct? This requires mathematical induction to exert its power. Assuming that the formula we compiled is true at the kth number, furthermore if it is proved correct at the k + 1th, then the formula we have compiled is verified correct.</p> <p>So what is the connection between mathematical induction and recursion? We just said that the recursive code must have an end condition. If not, it will fall into endless self-calling hell until the memory exhausted. The difficulty of mathematical proof is that you can try to have a finite number of cases, but it is difficult to extend your conclusion to infinity. Here you can see the connection-infinite.</p> <p>The essence of recursive code is to call itself to solve smaller sub-problems until the end condition is reached. The reason why mathematical induction is useful is to continuously increase our guess by one, and expand the size of the conclusion, without end condition. So by extending the conclusion to infinity, the proof of the correctness of the guess is completed.</p> <h3 id="why-learn-recursion">Why learn recursion</h3> <p>First to train the ability to think reversely. Recursive thinking is the thinking of normal people, always looking at the problems in front of them and thinking about solutions, and the solution is the future tense; Recursive thinking forces us to think reversely, see the end of the problem, and treat the problem-solving process as the past tense.</p> <p>Second, practice analyzing the structure of the problem. When the problem can be broken down into sub problems of the same structure, you can acutely find this feature, and then solve it efficiently.</p> <p>Third, go beyond the details and look at the problem as a whole. Let's talk about merge and sort. In fact, you can divide the left and right areas without recursion, but the cost is that the code is extremely difficult to understand. Take a look at the code below (merge sorting will be described later. You can understand the meaning here, and appreciate the beauty of recursion).</p> <div class="sourceCode" id="cb2"> <pre data-filter-output="(out)" class="sourceCode java"><code class="sourceCode java"><a class="sourceLine" id="cb2-1" title="1"><span class="lang-js dt">void</span> <span class="lang-js fu">sort</span>(<span class="lang-js bu">Comparable</span>[] a){ </a> <a class="sourceLine" id="cb2-2" title="2"> <span class="lang-js dt">int</span> N = a.<span class="lang-js fu">length</span>;</a> <a class="sourceLine" id="cb2-3" title="3"> <span class="lang-js co">// So complicated! It shows disrespect for sorting. I refuse to study such code.</span></a> <a class="sourceLine" id="cb2-4" title="4"> <span class="lang-js kw">for</span> (<span class="lang-js dt">int</span> sz = <span class="lang-js dv">1</span>; sz &lt; N; sz = sz + sz)</a> <a class="sourceLine" id="cb2-5" title="5"> <span class="lang-js kw">for</span> (<span class="lang-js dt">int</span> lo = <span class="lang-js dv">0</span>; lo &lt; N - sz; lo += sz + sz)</a> <a class="sourceLine" id="cb2-6" title="6"> <span class="lang-js fu">merge</span>(a, lo, lo + sz - <span class="lang-js dv">1</span>, <span class="lang-js bu">Math</span>.<span class="lang-js fu">min</span>(lo + sz + sz - <span class="lang-js dv">1</span>, N - <span class="lang-js dv">1</span>));</a> <a class="sourceLine" id="cb2-7" title="7">}</a> <a class="sourceLine" id="cb2-8" title="8"></a> <a class="sourceLine" id="cb2-9" title="9"><span class="lang-js co">/* I prefer recursion, simple and beautiful */</span></a> <a class="sourceLine" id="cb2-10" title="10"><span class="lang-js dt">void</span> <span class="lang-js fu">sort</span>(<span class="lang-js bu">Comparable</span>[] a, <span class="lang-js dt">int</span> lo, <span class="lang-js dt">int</span> hi) {</a> <a class="sourceLine" id="cb2-11" title="11"> <span class="lang-js kw">if</span> (lo &gt;= hi) <span class="lang-js kw">return</span>;</a> <a class="sourceLine" id="cb2-12" title="12"> <span class="lang-js dt">int</span> mid = lo + (hi - lo) / <span class="lang-js dv">2</span>;</a> <a class="sourceLine" id="cb2-13" title="13"> <span class="lang-js fu">sort</span>(a, lo, mid); <span class="lang-js co">// soft left part</span></a> <a class="sourceLine" id="cb2-14" title="14"> <span class="lang-js fu">sort</span>(a, mid + <span class="lang-js dv">1</span>, hi); <span class="lang-js co">// soft right part</span></a> <a class="sourceLine" id="cb2-15" title="15"> <span class="lang-js fu">merge</span>(a, lo, mid, hi); <span class="lang-js co">// merge the two sides</span></a> <a class="sourceLine" id="cb2-16" title="16">}</a></code></pre> </div> <p>Looks simple and beautiful is one aspect, the key is <strong>very interpretable</strong>: sort the left half, sort the right half, and finally merge the two sides. The non-recursive version looks unintelligible, full of various incomprehensible boundary calculation details, is particularly prone to bugs and difficult to debug. Life is short, i prefer the recursive version.</p> <p>Obviously, sometimes recursive processing is efficient, such as merge sort, <strong>sometimes inefficient</strong>, such as counting the hair of Monkey King, because the stack consumes extra space but simple inference does not consume space. Example below gives a linked list header and calculate its length:</p> <div class="sourceCode" id="cb3"> <pre data-filter-output="(out)" class="sourceCode java"><code class="sourceCode java"><a class="sourceLine" id="cb3-1" title="1"><span class="lang-js co">/* Typical recursive traversal framework requires extra space O(1) */</span></a> <a class="sourceLine" id="cb3-2" title="2"><span class="lang-js kw">public</span> <span class="lang-js dt">int</span> <span class="lang-js fu">size</span>(<span class="lang-js bu">Node</span> head) {</a> <a class="sourceLine" id="cb3-3" title="3"> <span class="lang-js dt">int</span> size = <span class="lang-js dv">0</span>;</a> <a class="sourceLine" id="cb3-4" title="4"> <span class="lang-js kw">for</span> (<span class="lang-js bu">Node</span> p = head; p != <span class="lang-js kw">null</span>; p = p.<span class="lang-js fu">next</span>) size++;</a> <a class="sourceLine" id="cb3-5" title="5"> <span class="lang-js kw">return</span> size;</a> <a class="sourceLine" id="cb3-6" title="6">}</a> <a class="sourceLine" id="cb3-7" title="7"><span class="lang-js co">/* I insist on recursion facing every problem. I need extra space O(N) */</span></a> <a class="sourceLine" id="cb3-8" title="8"><span class="lang-js kw">public</span> <span class="lang-js dt">int</span> <span class="lang-js fu">size</span>(<span class="lang-js bu">Node</span> head) {</a> <a class="sourceLine" id="cb3-9" title="9"> <span class="lang-js kw">if</span> (head == <span class="lang-js kw">null</span>) <span class="lang-js kw">return</span> <span class="lang-js dv">0</span>;</a> <a class="sourceLine" id="cb3-10" title="10"> <span class="lang-js kw">return</span> <span class="lang-js fu">size</span>(head.<span class="lang-js fu">next</span>) + <span class="lang-js dv">1</span>;</a> <a class="sourceLine" id="cb3-11" title="11">}</a></code></pre> </div> <h3 id="tips-for-writing-recursion">Tips for writing recursion</h3> <p>My point of view: <strong>Understand what a function does and believe it can accomplish this task. Don't try to jump into the details.</strong> Do not jump into this function to try to explore more details, otherwise you will fall into infinite details and cannot extricate yourself. The human brain carries tiny sized stack!</p> <p>Let's start with the simplest example: traversing a binary tree.</p> <div class="sourceCode" id="cb4"> <pre data-filter-output="(out)" class="sourceCode cpp"><code class="sourceCode cpp"><a class="sourceLine" id="cb4-1" title="1"><span class="lang-js dt">void</span> traverse(TreeNode* root) {</a> <a class="sourceLine" id="cb4-2" title="2"> <span class="lang-js cf">if</span> (root == <span class="lang-js kw">nullptr</span>) <span class="lang-js cf">return</span>;</a> <a class="sourceLine" id="cb4-3" title="3"> traverse(root-&gt;left);</a> <a class="sourceLine" id="cb4-4" title="4"> traverse(root-&gt;right);</a> <a class="sourceLine" id="cb4-5" title="5">}</a></code></pre> </div> <p>Above few lines of code are enough to wipe out any binary tree. What I want to say is that for the recursive function <code class="language-javascript">traverse (root)</code> , we just need to believe: give it a root node <code class="language-javascript">root</code> , and it can traverse the whole tree. Since this function is written for this specific purpose, so we just need to dump the left and right nodes of this node to this function, because I believe it can surely complete the task. What about traversing an N-fork tree? It's too simple, exactly the same as a binary tree! </p> <div class="sourceCode" id="cb5"> <pre data-filter-output="(out)" class="sourceCode cpp"><code class="sourceCode cpp"><a class="sourceLine" id="cb5-1" title="1"><span class="lang-js dt">void</span> traverse(TreeNode* root) {</a> <a class="sourceLine" id="cb5-2" title="2"> <span class="lang-js cf">if</span> (root == <span class="lang-js kw">nullptr</span>) <span class="lang-js cf">return</span>;</a> <a class="sourceLine" id="cb5-3" title="3"> <span class="lang-js cf">for</span> (child : root-&gt;children)</a> <a class="sourceLine" id="cb5-4" title="4"> traverse(child);</a> <a class="sourceLine" id="cb5-5" title="5">}</a></code></pre> </div> <p>As for pre-order, mid-order, post-order traversal, they are all obvious. For N-fork tree, there is obviously no in-order traversal.</p> <p>The following <strong>explains a problem from LeetCode in detail</strong>: Given a binary tree and a target value, the values in every node is positive or negative, return the number of paths in the tree that are equal to the target value, let you write the pathSum function:</p> <pre data-filter-output="(out)" data-role="codeBlock" data-info="js" class="language-javascript data-line line-numbers data-user data-host data-prompt data-output" data-prismjs-copy="Copy !" data-download-link /> <code class="language-javascript">/* from LeetCode PathSum III: https://leetcode.com/problems/path-sum-iii/ */ root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8 10 / \ 5 -3 / \ \ 3 2 11 / \ \ 3 -2 1 Return 3. The paths that sum to 8 are: 1. 5 -&gt; 3 2. 5 -&gt; 2 -&gt; 1 3. -3 -&gt; 11 </code></pre> <div class="sourceCode" id="cb7"> <pre data-filter-output="(out)" class="sourceCode cpp"><code class="sourceCode cpp"><a class="sourceLine" id="cb7-1" title="1"><span class="lang-js co">/* It doesn&#39;t matter if you don&#39;t understand, there is a more detailed analysis version below, which highlights the conciseness and beauty of recursion. */</span></a> <a class="sourceLine" id="cb7-2" title="2"><span class="lang-js dt">int</span> pathSum(TreeNode root, <span class="lang-js dt">int</span> sum) {</a> <a class="sourceLine" id="cb7-3" title="3"> <span class="lang-js cf">if</span> (root == null) <span class="lang-js cf">return</span> <span class="lang-js dv">0</span>;</a> <a class="sourceLine" id="cb7-4" title="4"> <span class="lang-js cf">return</span> count(root, sum) + </a> <a class="sourceLine" id="cb7-5" title="5"> pathSum(root.left, sum) + pathSum(root.right, sum);</a> <a class="sourceLine" id="cb7-6" title="6">}</a> <a class="sourceLine" id="cb7-7" title="7"><span class="lang-js dt">int</span> count(TreeNode node, <span class="lang-js dt">int</span> sum) {</a> <a class="sourceLine" id="cb7-8" title="8"> <span class="lang-js cf">if</span> (node == null) <span class="lang-js cf">return</span> <span class="lang-js dv">0</span>;</a> <a class="sourceLine" id="cb7-9" title="9"> <span class="lang-js cf">return</span> (node.val == sum) + </a> <a class="sourceLine" id="cb7-10" title="10"> count(node.left, sum - node.val) + count(node.right, sum - node.val);</a> <a class="sourceLine" id="cb7-11" title="11">}</a></code></pre> </div> <p>The problem may seem complicated, but the code is extremely concise, which is the charm of recursion. Let me briefly summarize the <strong>solution process</strong> of this problem:</p> <p>First of all, it is clear that to solve the problem of recursive tree, you must traverse the entire tree. So the traversal framework of the binary tree (recursively calling the function itself on the left and right children) must appear in the main function pathSum. And then, what should they do for each node? They should see how many eligible paths they and their little children have under their feet. Well, this question is clear.</p> <p>According to the techniques mentioned earlier, define what each recursive function should do based on the analysis just now:</p> <p>PathSum function: Give it a node and a target value. It returns the total number of paths in the tree rooted at this node and the target value.</p> <p>Count function: Give it a node and a target value. It returns a tree rooted at this node, and can make up the total number of paths starting with the node and the target value.</p> <div class="sourceCode" id="cb8"> <pre data-filter-output="(out)" class="sourceCode cpp"><code class="sourceCode cpp"><a class="sourceLine" id="cb8-1" title="1"><span class="lang-js co">/* With above tips, comment out the code in detail */</span></a> <a class="sourceLine" id="cb8-2" title="2"><span class="lang-js dt">int</span> pathSum(TreeNode root, <span class="lang-js dt">int</span> sum) {</a> <a class="sourceLine" id="cb8-3" title="3"> <span class="lang-js cf">if</span> (root == null) <span class="lang-js cf">return</span> <span class="lang-js dv">0</span>;</a> <a class="sourceLine" id="cb8-4" title="4"> <span class="lang-js dt">int</span> pathImLeading = count(root, sum); <span class="lang-js co">// Number of paths beginning with itself</span></a> <a class="sourceLine" id="cb8-5" title="5"> <span class="lang-js dt">int</span> leftPathSum = pathSum(root.left, sum); <span class="lang-js co">// The total number of paths on the left (Believe he can figure it out)</span></a> <a class="sourceLine" id="cb8-6" title="6"> <span class="lang-js dt">int</span> rightPathSum = pathSum(root.right, sum); <span class="lang-js co">// The total number of paths on the right (Believe he can figure it out)</span></a> <a class="sourceLine" id="cb8-7" title="7"> <span class="lang-js cf">return</span> leftPathSum + rightPathSum + pathImLeading;</a> <a class="sourceLine" id="cb8-8" title="8">}</a> <a class="sourceLine" id="cb8-9" title="9"><span class="lang-js dt">int</span> count(TreeNode node, <span class="lang-js dt">int</span> sum) {</a> <a class="sourceLine" id="cb8-10" title="10"> <span class="lang-js cf">if</span> (node == null) <span class="lang-js cf">return</span> <span class="lang-js dv">0</span>;</a> <a class="sourceLine" id="cb8-11" title="11"> <span class="lang-js co">// Can I stand on my own as a separate path?</span></a> <a class="sourceLine" id="cb8-12" title="12"> <span class="lang-js dt">int</span> isMe = (node.val == sum) ? <span class="lang-js dv">1</span> : <span class="lang-js dv">0</span>;</a> <a class="sourceLine" id="cb8-13" title="13"> <span class="lang-js co">// Left brother, how many sum-node.val can you put together?</span></a> <a class="sourceLine" id="cb8-14" title="14"> <span class="lang-js dt">int</span> leftBrother = count(node.left, sum - node.val); </a> <a class="sourceLine" id="cb8-15" title="15"> <span class="lang-js co">// Right brother, how many sum-node.val can you put together?</span></a> <a class="sourceLine" id="cb8-16" title="16"> <span class="lang-js dt">int</span> rightBrother = count(node.right, sum - node.val);</a> <a class="sourceLine" id="cb8-17" title="17"> <span class="lang-js cf">return</span> isMe + leftBrother + rightBrother; <span class="lang-js co">// all count i can make up</span></a> <a class="sourceLine" id="cb8-18" title="18">}</a></code></pre> </div> <p>Again, understand what each function can do and trust that they can do it.</p> <p>In summary, the binary tree traversal framework provided by the PathSum function calls the count function for each node during the traversal. Can you see the pre-order traversal (the order is the same for this question)? The count function is also a binary tree traversal, used to find the target value path starting with this node. Understand it deeply!</p> <h2 id="divide-and-conquer-algorithm">Divide and conquer algorithm</h2> <p><strong>Merge and sort</strong>, typical divide-and-conquer algorithm; divide-and-conquer, typical recursive structure.</p> <p>The divide-and-conquer algorithm can go in three steps: decomposition-&gt; solve-&gt; merge</p> <ol type="1"> <li>Decompose the original problem into sub-problems with the same structure.</li> <li>After decomposing to an easy-to-solve boundary, perform a recursive solution.</li> <li>Combine the solutions of the subproblems into the solutions of the original problem.</li> </ol> <p>To merge and sort, let's call this function <code class="language-javascript">merge_sort</code> . According to what we said above, we must clarify the responsibility of the function, that is, <strong>sort an incoming array</strong>. OK, can this problem be solved? Of course! Sorting an array is just the same to sorting the two halves of the array separately, and then merging the two halves.</p> <div class="sourceCode" id="cb9"> <pre data-filter-output="(out)" class="sourceCode cpp"><code class="sourceCode cpp"><a class="sourceLine" id="cb9-1" title="1"><span class="lang-js dt">void</span> merge_sort(an array) {</a> <a class="sourceLine" id="cb9-2" title="2"> <span class="lang-js cf">if</span> (some tiny array easy to solve) <span class="lang-js cf">return</span>;</a> <a class="sourceLine" id="cb9-3" title="3"> merge_sort(left half array);</a> <a class="sourceLine" id="cb9-4" title="4"> merge_sort(right half array);</a> <a class="sourceLine" id="cb9-5" title="5"> merge(left half array, right half array);</a> <a class="sourceLine" id="cb9-6" title="6">}</a></code></pre> </div> <p>Well, this algorithm is like this, there is no difficulty at all. Remember what I said before, believe in the function's ability, and pass it to him half of the array, then the half of the array is already sorted. Have you found it's a binary tree traversal template? Why it is postorder traversal? Because the routine of our divide-and-conquer algorithm is <strong>decomposition-&gt; solve (bottom)-&gt; merge (backtracking)</strong> Ah, first left and right decomposition, and then processing merge, backtracking is popping stack, which is equivalent to post-order traversal. As for the <code class="language-javascript">merge</code> function, referring to the merging of two ordered linked lists, they are exactly the same, and the code is directly posted below.</p> <p>Let's refer to the Java code in book <code class="language-javascript">Algorithm 4</code> below, which is pretty. This shows that not only algorithmic thinking is important, but coding skills are also very important! Think more and imitate more.</p> <div class="sourceCode" id="cb10"> <pre data-filter-output="(out)" class="sourceCode java"><code class="sourceCode java"><a class="sourceLine" id="cb10-1" title="1"><span class="lang-js kw">public</span> <span class="lang-js kw">class</span> Merge {</a> <a class="sourceLine" id="cb10-2" title="2"> <span class="lang-js co">// Do not construct new arrays in the merge function, because the merge function will be called multiple times, affecting performance.Construct a large enough array directly at once, concise and efficient.</span></a> <a class="sourceLine" id="cb10-3" title="3"> <span class="lang-js kw">private</span> <span class="lang-js dt">static</span> <span class="lang-js bu">Comparable</span>[] aux;</a> <a class="sourceLine" id="cb10-4" title="4"></a> <a class="sourceLine" id="cb10-5" title="5"> <span class="lang-js kw">public</span> <span class="lang-js dt">static</span> <span class="lang-js dt">void</span> <span class="lang-js fu">sort</span>(<span class="lang-js bu">Comparable</span>[] a) {</a> <a class="sourceLine" id="cb10-6" title="6"> aux = <span class="lang-js kw">new</span> <span class="lang-js bu">Comparable</span>[a.<span class="lang-js fu">length</span>];</a> <a class="sourceLine" id="cb10-7" title="7"> <span class="lang-js fu">sort</span>(a, <span class="lang-js dv">0</span>, a.<span class="lang-js fu">length</span> - <span class="lang-js dv">1</span>);</a> <a class="sourceLine" id="cb10-8" title="8"> }</a> <a class="sourceLine" id="cb10-9" title="9"></a> <a class="sourceLine" id="cb10-10" title="10"> <span class="lang-js kw">private</span> <span class="lang-js dt">static</span> <span class="lang-js dt">void</span> <span class="lang-js fu">sort</span>(<span class="lang-js bu">Comparable</span>[] a, <span class="lang-js dt">int</span> lo, <span class="lang-js dt">int</span> hi) {</a> <a class="sourceLine" id="cb10-11" title="11"> <span class="lang-js kw">if</span> (lo &gt;= hi) <span class="lang-js kw">return</span>;</a> <a class="sourceLine" id="cb10-12" title="12"> <span class="lang-js dt">int</span> mid = lo + (hi - lo) / <span class="lang-js dv">2</span>;</a> <a class="sourceLine" id="cb10-13" title="13"> <span class="lang-js fu">sort</span>(a, lo, mid);</a> <a class="sourceLine" id="cb10-14" title="14"> <span class="lang-js fu">sort</span>(a, mid + <span class="lang-js dv">1</span>, hi);</a> <a class="sourceLine" id="cb10-15" title="15"> <span class="lang-js fu">merge</span>(a, lo, mid, hi);</a> <a class="sourceLine" id="cb10-16" title="16"> }</a> <a class="sourceLine" id="cb10-17" title="17"></a> <a class="sourceLine" id="cb10-18" title="18"> <span class="lang-js kw">private</span> <span class="lang-js dt">static</span> <span class="lang-js dt">void</span> <span class="lang-js fu">merge</span>(<span class="lang-js bu">Comparable</span>[] a, <span class="lang-js dt">int</span> lo, <span class="lang-js dt">int</span> mid, <span class="lang-js dt">int</span> hi) {</a> <a class="sourceLine" id="cb10-19" title="19"> <span class="lang-js dt">int</span> i = lo, j = mid + <span class="lang-js dv">1</span>;</a> <a class="sourceLine" id="cb10-20" title="20"> <span class="lang-js kw">for</span> (<span class="lang-js dt">int</span> k = lo; k &lt;= hi; k++)</a> <a class="sourceLine" id="cb10-21" title="21"> aux[k] = a[k];</a> <a class="sourceLine" id="cb10-22" title="22"> <span class="lang-js kw">for</span> (<span class="lang-js dt">int</span> k = lo; k &lt;= hi; k++) {</a> <a class="sourceLine" id="cb10-23" title="23"> <span class="lang-js kw">if</span> (i &gt; mid) { a[k] = aux[j++]; }</a> <a class="sourceLine" id="cb10-24" title="24"> <span class="lang-js kw">else</span> <span class="lang-js kw">if</span> (j &gt; hi) { a[k] = aux[i++]; }</a> <a class="sourceLine" id="cb10-25" title="25"> <span class="lang-js kw">else</span> <span class="lang-js kw">if</span> (<span class="lang-js fu">less</span>(aux[j], aux[i])) { a[k] = aux[j++]; }</a> <a class="sourceLine" id="cb10-26" title="26"> <span class="lang-js kw">else</span> { a[k] = aux[i++]; }</a> <a class="sourceLine" id="cb10-27" title="27"> }</a> <a class="sourceLine" id="cb10-28" title="28"> }</a> <a class="sourceLine" id="cb10-29" title="29"></a> <a class="sourceLine" id="cb10-30" title="30"> <span class="lang-js kw">private</span> <span class="lang-js dt">static</span> <span class="lang-js dt">boolean</span> <span class="lang-js fu">less</span>(<span class="lang-js bu">Comparable</span> v, <span class="lang-js bu">Comparable</span> w) {</a> <a class="sourceLine" id="cb10-31" title="31"> <span class="lang-js kw">return</span> v.<span class="lang-js fu">compareTo</span>(w) &lt; <span class="lang-js dv">0</span>;</a> <a class="sourceLine" id="cb10-32" title="32"> }</a> <a class="sourceLine" id="cb10-33" title="33">}</a></code></pre> </div> <p>LeetCode has a special exercise of the divide-and-conquer algorithm. Copy the link below to web browser and have a try:</p> <p>https://leetcode.com/tag/divide-and-conquer/</p> <p>Prompt: write a function that will reverse a string:</p> <p>var reverse = function(string){<br /> if(string.length &lt; 2){</p> <pre data-filter-output="(out)" data-role="codeBlock" data-info="js" class="language-javascript data-line line-numbers data-user data-host data-prompt data-output" data-prismjs-copy="Copy !" data-download-link /> <code class="language-javascript">return string; </code></pre> <p>}<br /> var first = string[0]<br /> var last = string[string.length-1]; return last +reverse(string.slice(1, string.length-1)) + first; }; reverse('abcdef'); //returns 'fedcba'</p> <p><strong>//explain what a recursive function is</strong></p> <p><strong><em>A function that calls itself</em></strong> is a recursive function.</p> <p>If a function calls itself… then that function calls itself… then that function calls itself… well… then we have fallen into an infinite loop (a very unproductive place to be). To benefit from recursive calls, we need to be careful to include to give our interpreter a way to break out of the cycle of recursive function calls; we call this a <strong><em>base case</em></strong>.</p> <p>The base case in the solution code above is as simple as testing that the length of the argument is less than 2… and if it is, returning the the value of that argument.</p> <p>Notice how each time we recursively call the reverse function, we are passing it a shorter string argument… so each recursive call is getting us closer to hitting our <strong><em>base case</em></strong>.</p> <p><strong>//visualize the interpreter's path through recursive function calls</strong></p> <figure> <img src="https://miro.medium.com/max/60/1*J4FL6LpLY1AXy_KPFdREKw.png?q=20" alt="Image for post" /> <figcaption>Image for post</figcaption> </figure> <figure> <img src="https://miro.medium.com/max/1810/1*J4FL6LpLY1AXy_KPFdREKw.png" alt="Image for post" /> <figcaption>Image for post</figcaption> </figure> <p>Slow down and follow the interpreter through its execution of your algorithm (thanks to PythonTutor.com)</p> <p>Python Tutor is an excellent resource for learning to visualize and trace variable values through the multiple execution contexts of a recursive function's invocation.</p> <p><em>Try it now with these simple steps:</em></p> <ol type="1"> <li><em>copy the solution code from above</em></li> <li><em>go over to</em> <a href="http://pythontutor.com/javascript.html#mode=edit"><em>http://pythontutor.com/javascript.html#mode=edit</em></a> </li> <li><em>paste the solution code into the editor</em></li> <li><em>click the "Visualize Execution" button</em></li> <li><em>progress through the execution with the "forward" button</em></li> </ol> <p><strong>//when can a recursive function help me?</strong></p> <p>So if I hope that at this point that you are thinking: there is a <strong><em>better</em></strong> way to reverse a function, or there is a <strong><em>simpler</em></strong> way to reverse a string…</p> <p>First off… <strong><em>simpler is better.</em></strong> Writing good code isn't about being clever or fancy; good code is about writing code that works, that makes sense to as many other minds as possible, that is time efficient, and that is memory efficient (in order of importance). As new programers, the first of these criteria is obvious, and the last two are given way too much weight. It's the second of these criteria that needs to carry much more weight in our minds and deserves the most attention. Recursive functions can be a powerful tool in helping us write clear and simple solutions.</p> <p>To be clear: recursion is not about being fancy or clever… it is an important skill to wrestle with early because there will be many scenarios when employing recursion will allow for a simpler and more reliable solution than would be possible without recursive functions.</p> <p><strong>//more useful example</strong></p> <p>Prompt: check to see if a binary-search-tree contains a value</p> <p>var searchBST = function(tree, num){<br /> if(tree.val === num){</p> <pre data-filter-output="(out)" data-role="codeBlock" data-info="js" class="language-javascript data-line line-numbers data-user data-host data-prompt data-output" data-prismjs-copy="Copy !" data-download-link /> <code class="language-javascript">return true </code></pre> <p>} else if(num &gt; tree.val){</p> <pre data-filter-output="(out)" data-role="codeBlock" data-info="js" class="language-javascript data-line line-numbers data-user data-host data-prompt data-output" data-prismjs-copy="Copy !" data-download-link /> <code class="language-javascript">if(tree.right === null){ return false; } else{ return searchBST(tree.right, num); } </code></pre> <p>} else{</p> <pre data-filter-output="(out)" data-role="codeBlock" data-info="js" class="language-javascript data-line line-numbers data-user data-host data-prompt data-output" data-prismjs-copy="Copy !" data-download-link /> <code class="language-javascript">if(tree.left === null){ return false; } else{ return searchBST(tree.left, num); } </code></pre> <p>}<br /> }; var tree = {val: 9,</p> <pre data-filter-output="(out)" data-role="codeBlock" data-info="js" class="language-javascript data-line line-numbers data-user data-host data-prompt data-output" data-prismjs-copy="Copy !" data-download-link /> <code class="language-javascript"> left: {val: 5, left: null, right: {val: 7, left: null, right: null} }, right: {val: 20, left: {val: 16, left: null, right: {val: 18, left: null, right: null} }, right: null} };searchBST(tree, 18) // return true </code></pre> <p>searchBST(tree, 4) // return false</p> <p>When traversing trees and many other other non-primative data structures, recursion allows us to define a clear algorithm that elegantly handles uncertainty and complexity. Without recursion, it would be impossible to write a single function that could search a binary search tree of any size and state… yet by employing recursion, we can write a concise algorithm that will traverse any binary search tree and determine if it contains a value or not.</p> <p>Take a moment to analyze how recursion is used in this example by tracing the interpreters path through this solution. Just as we did for the reverse function above, paste this binary search tree code snippet into the editor at <a href="http://pythontutor.com/javascript.html#mode=display">http://pythontutor.com/javascript.html#mode=display</a> </p> <p>In this function definition, there are three base cases that will return a value instead of recursively calling the searchBST function… can you find them?</p> <p>//now go practice using recursion</p> <h1 id="data-structures-and-algorithms" data-ignore="true"><br><em>Data Structures and Algorithms</em> </h1> <hr /> <!-- code_chunk_output --> <p><a href="#big-o-"><strong>Big O </strong></a> <a href="#memoization-and-tabulation-"><strong>Memoization And Tabulation </strong></a> - <a href="#recursion-videos">Recursion Videos</a> - <a href="#curating-complexity-a-guide-to-big-o-notation">Curating Complexity: A Guide to Big-O Notation</a> - <a href="#why-big-o">Why Big-O?</a> - <a href="#big-o-notation">Big-O Notation</a> - <a href="#common-complexity-classes">Common Complexity Classes</a> - <a href="#the-seven-major-classes">The seven major classes</a> - <a href="#memoization">Memoization</a> - <a href="#memoizing-factorial">Memoizing factorial</a> - <a href="#memoizing-the-fibonacci-generator">Memoizing the Fibonacci generator</a> - <a href="#the-memoization-formula">The memoization formula</a> - <a href="#tabulation">Tabulation</a> - <a href="#tabulating-the-fibonacci-number">Tabulating the Fibonacci number</a> - <a href="#aside-refactoring-for-o1-space">Aside: Refactoring for O(1) Space</a> - <a href="#analysis-of-linear-search">Analysis of Linear Search</a> - <a href="#analysis-of-binary-search">Analysis of Binary Search</a> - <a href="#analysis-of-the-merge-sort">Analysis of the Merge Sort</a> - <a href="#analysis-of-bubble-sort">Analysis of Bubble Sort</a> - <a href="#leetcodecom">LeetCode.com</a> - <a href="#memoization-problems">Memoization Problems</a> - <a href="#tabulation-problems">Tabulation Problems</a> </p> <p><a href="#sorting-algorithms-"><strong>Sorting Algorithms </strong></a> - <a href="#bubble-sort">Bubble Sort</a> - <a href="#_butthenwhy-are-we_"><em>"But…then…why are we…"</em></a> - <a href="#the-algorithm-bubbles-up">The algorithm bubbles up</a> - <a href="#how-does-a-pass-of-bubble-sort-work">How does a pass of Bubble Sort work?</a> - <a href="#ending-the-bubble-sort">Ending the Bubble Sort</a> - <a href="#pseudocode-for-bubble-sort">Pseudocode for Bubble Sort</a> - <a href="#selection-sort">Selection Sort</a> - <a href="#the-algorithm-select-the-next-smallest">The algorithm: select the next smallest</a> - <a href="#the-pseudocode">The pseudocode</a> - <a href="#insertion-sort">Insertion Sort</a> - <a href="#the-algorithm-insert-into-the-sorted-region">The algorithm: insert into the sorted region</a> - <a href="#the-steps">The Steps</a> - <a href="#the-pseudocode-1">The pseudocode</a> - <a href="#merge-sort">Merge Sort</a> - <a href="#the-algorithm-divide-and-conquer">The algorithm: divide and conquer</a> - <a href="#quick-sort">Quick Sort</a> - <a href="#how-does-it-work">How does it work?</a> - <a href="#the-algorithm-divide-and-conquer-1">The algorithm: divide and conquer</a> - <a href="#the-pseudocode-2">The pseudocode</a> - <a href="#binary-search">Binary Search</a> - <a href="#the-algorithm-check-the-middle-and-half-the-search-space">The Algorithm: "check the middle and half the search space"</a> - <a href="#the-pseudocode-3">The pseudocode</a> - <a href="#bubble-sort-analysis">Bubble Sort Analysis</a> - <a href="#time-complexity-onsup2sup">Time Complexity: O(n2)</a> - <a href="#space-complexity-o1">Space Complexity: O(1)</a> - <a href="#when-should-you-use-bubble-sort">When should you use Bubble Sort?</a> - <a href="#selection-sort-analysis">Selection Sort Analysis</a> - <a href="#selection-sort-js-implementation">Selection Sort JS Implementation</a> - <a href="#time-complexity-analysis">Time Complexity Analysis</a> - <a href="#space-complexity-analysis-o1">Space Complexity Analysis: O(1)</a> - <a href="#when-should-we-use-selection-sort">When should we use Selection Sort?</a> - <a href="#insertion-sort-analysis">Insertion Sort Analysis</a> - <a href="#time-and-space-complexity-analysis">Time and Space Complexity Analysis</a> - <a href="#when-should-you-use-insertion-sort">When should you use Insertion Sort?</a> - <a href="#merge-sort-analysis">Merge Sort Analysis</a> - <a href="#full-code">Full code</a> - <a href="#merging-two-sorted-arrays">Merging two sorted arrays</a> - <a href="#divide-and-conquer-step-by-step">Divide and conquer, step-by-step</a> - <a href="#time-and-space-complexity-analysis-1">Time and Space Complexity Analysis</a> - <a href="#quick-sort-analysis">Quick Sort Analysis</a> - <a href="#time-and-space-complexity-analysis-2">Time and Space Complexity Analysis</a> - <a href="#binary-search-analysis">Binary Search Analysis</a> - <a href="#time-and-space-complexity-analysis-3">Time and Space Complexity Analysis</a> - <a href="#practice-bubble-sort">Practice: Bubble Sort</a> - <a href="#practice-selection-sort">Practice: Selection Sort</a> - <a href="#practice-insertion-sort">Practice: Insertion Sort</a> - <a href="#practice-merge-sort">Practice: Merge Sort</a> - <a href="#practice-quick-sort-2">Practice: Quick Sort</a> - <a href="#practice-binary-search">Practice: Binary Search</a> </p> <p><a href="#lists-stacks-and-queues-"><strong>Lists, Stacks, and Queues </strong></a> - <a href="#linked-lists">Linked Lists</a> - <a href="#what-is-a-linked-list">What is a Linked List?</a> - <a href="#types-of-linked-lists">Types of Linked Lists</a> - <a href="#linked-list-methods">Linked List Methods</a> - <a href="#time-and-space-complexity-analysis-4">Time and Space Complexity Analysis</a> - <a href="#time-complexity-access-and-search">Time Complexity - Access and Search</a> - <a href="#time-complexity-insertion-and-deletion">Time Complexity - Insertion and Deletion</a> - <a href="#space-complexity-1">Space Complexity</a> - <a href="#stacks-and-queues">Stacks and Queues</a> - <a href="#what-is-a-stack">What is a Stack?</a> - <a href="#what-is-a-queue">What is a Queue?</a> - <a href="#stack-and-queue-properties">Stack and Queue Properties</a> - <a href="#stack-methods">Stack Methods</a> - <a href="#queue-methods">Queue Methods</a> - <a href="#time-and-space-complexity-analysis-5">Time and Space Complexity Analysis</a> - <a href="#when-should-we-use-stacks-and-queues">When should we use Stacks and Queues?</a> - <a </p> <p><a href="#graphs-and-heaps-"><strong>Graphs and Heaps </strong></a> - <a href="#introduction-to-heaps">Introduction to Heaps</a> - <a href="#binary-heap-implementation">Binary Heap Implementation</a> - <a href="#heap-sort">Heap Sort</a> - <a href="#in-place-heap-sort">In-Place Heap Sort</a> - </p> <!-- /code_chunk_output --> <hr /> <h1 id="big-o-">Big O </h1> <p><strong>The objective of this lesson</strong> is get you comfortable with identifying the time and space complexity of code you see. Being able to diagnose time complexity for algorithms is an essential for interviewing software engineers.</p> <p>At the end of this, you will be able to</p> <ol type="1"> <li>Order the common complexity classes according to their growth rate</li> <li>Identify the complexity classes of common sort methods</li> <li>Identify complexity classes of codeable with identifying the time and space complexity of code you see. Being able to diagnose time complexity for algorithms is an essential for interviewing software engineers. </li> </ol> <p>At the end of this, you will be able to</p> <ol type="1"> <li>Order the common complexity classes according to their growth rate</li> <li>Identify the complexity classes of common sort methods</li> <li>Identify complexity classes of code</li> </ol> <hr /> <h1 id="memoization-and-tabulation-">Memoization And Tabulation </h1> <p><strong>The objective of this lesson</strong> is to give you a couple of ways to optimize a computation (algorithm) from a higher complexity class to a lower complexity class. Being able to optimize algorithms is an essential for interviewing software engineers.</p> <p>At the end of this, you will be able to</p> <ol type="1"> <li>Apply memoization to recursive problems to make them less than polynomial time.</li> <li>Apply tabulation to iterative problems to make them less than polynomial time.** is to give you a couple of ways to optimize a computation (algorithm) from a higher complexity class to a lower complexity class. Being able to optimize algorithms is an essential for interviewing software engineers.</li> </ol> <p>At the end of this, you will be able to</p> <ol type="1"> <li>Apply memoization to recursive problems to make them less than polynomial time.</li> <li>Apply tabulation to iterative problems to make them less than polynomial time.</li> </ol> <hr /> <h1 id="recursion-videos">Recursion Videos</h1> <p>A lot of algorithms that we use in the upcoming days will use recursion. The next two videos are just helpful reminders about recursion so that you can get that thought process back into your brain.</p> <hr /> <h1 id="big-o-by-colt-steele">Big-O By Colt Steele</h1> <p>Colt Steele provides a very nice, non-mathy introduction to Big-O notation. Please watch this so you can get the easy introduction. Big-O is, by its very nature, math based. It's good to get an understanding before jumping in to math expressions.</p> <p><a href="https://www.youtube.com/embed/kS_gr2_-ws8">Complete Beginner's Guide to Big O Notation</a>