<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ #big o notation - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ #big o notation - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 09 Jun 2026 04:39:01 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/big-o-notation/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Big O Cheat Sheet – Time Complexity Chart ]]>
                </title>
                <description>
                    <![CDATA[ An algorithm is a set of well-defined instructions for solving a specific problem. You can solve these problems in various ways. This means that the method you use to arrive at the same solution may differ from mine, but we should both get the same r... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/big-o-cheat-sheet-time-complexity-chart/</link>
                <guid isPermaLink="false">66d45f6551f567b42d9f845f</guid>
                
                    <category>
                        <![CDATA[ algorithms ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #big o notation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ interview questions ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Joel Olawanle ]]>
                </dc:creator>
                <pubDate>Wed, 05 Oct 2022 15:00:53 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/10/cover-template--12-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>An algorithm is a set of well-defined instructions for solving a specific problem. You can solve these problems in various ways.</p>
<p>This means that the method you use to arrive at the same solution may differ from mine, but we should both get the same result.</p>
<p>Because there are various ways to solve a problem, there must be a way to evaluate these solutions or algorithms in terms of performance and efficiency (the time it will take for your algorithm to run/execute and the total amount of memory it will consume).</p>
<p>This is critical for programmers to ensure that their applications run properly and to help them write clean code.</p>
<p>This is where Big O Notation enters the picture. Big O Notation is a metric for determining the efficiency of an algorithm. It allows you to estimate how long your code will run on different sets of inputs and measure how effectively your code scales as the size of your input increases.</p>
<h2 id="heading-what-is-big-o">What is Big O?</h2>
<p>Big O, also known as Big O notation, represents an algorithm's worst-case complexity. It uses algebraic terms to describe the complexity of an algorithm.</p>
<p>Big O defines the runtime required to execute an algorithm by identifying how the performance of your algorithm will change as the input size grows. But it does not tell you how fast your algorithm's runtime is.</p>
<p>Big O notation measures the efficiency and performance of your algorithm using time and space complexity.</p>
<h3 id="heading-what-is-time-and-space-complexity">What is Time and Space Complexity?</h3>
<p>One major underlying factor affecting your program's performance and efficiency is the hardware, OS, and CPU you use.</p>
<p>But you don't consider this when you analyze an algorithm's performance. Instead, the time and space complexity as a function of the input's size are what matters.</p>
<p>An algorithm's time complexity specifies how long it will take to execute an algorithm <strong>as a function of its input size</strong>. Similarly, an algorithm's space complexity specifies the total amount of space or memory required to execute an algorithm <strong>as a function of the size of the input</strong>.</p>
<p>We will be focusing on time complexity in this guide. This will be an in-depth cheatsheet to help you understand how to calculate the time complexity for any algorithm.</p>
<h3 id="heading-why-is-time-complexity-a-function-of-its-input-size">Why is time complexity a function of its input size?</h3>
<p>To perfectly grasp the concept of "as a function of input size," imagine you have an algorithm that computes the sum of numbers based on your input. If your input is 4, it will add 1+2+3+4 to output 10; if your input is 5, it will output 15 (meaning 1+2+3+4+5).</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> calculateSum = <span class="hljs-function">(<span class="hljs-params">input</span>) =&gt;</span> {
  <span class="hljs-keyword">let</span> sum = <span class="hljs-number">0</span>;
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt;= input; i++) {
    sum += i;
  }
  <span class="hljs-keyword">return</span> sum;
};
</code></pre>
<p>In the code above, we have three statements:</p>
<p><img src="https://paper-attachments.dropbox.com/s_2D428973624E7FC84C7D69D11421DE762BEA6B6F3361231FCDCAE0425D14526F_1664881245657_Untitled.drawio+16.png" alt="s_2D428973624E7FC84C7D69D11421DE762BEA6B6F3361231FCDCAE0425D14526F_1664881245657_Untitled.drawio+16" width="600" height="400" loading="lazy"></p>
<p>Looking at the image above, we only have three statements. Still, because there is a loop, the second statement will be executed based on the input size, so if the input is four, the second statement (statement 2) will be executed four times, meaning the entire algorithm will run six (4 + 2) times.</p>
<p>In plain terms, the algorithm will run <strong>input + 2</strong> times, where input can be any number. This shows that <strong>it's expressed in terms of the input. In other words, it is a function of the input size</strong>.</p>
<p>In Big O, there are six major types of complexities (time and space):</p>
<ul>
<li><p>Constant: O(1)</p>
</li>
<li><p>Linear time: O(n)</p>
</li>
<li><p>Logarithmic time: O(n log n)</p>
</li>
<li><p>Quadratic time: O(n^2)</p>
</li>
<li><p>Exponential time: O(2^n)</p>
</li>
<li><p>Factorial time: O(n!)</p>
</li>
</ul>
<p>Before we look at examples for each time complexity, let's understand the Big O time complexity chart.</p>
<h2 id="heading-big-o-complexity-chart">Big O Complexity Chart</h2>
<p>The Big O chart, also known as the Big O graph, is an asymptotic notation used to express the complexity of an algorithm or its performance as a function of input size.</p>
<p>This helps programmers identify and fully understand the worst-case scenario and the execution time or memory required by an algorithm.</p>
<p>The <a target="_blank" href="bigocheatsheet.com">following graph</a> illustrates Big O complexity:</p>
<p><img src="https://paper-attachments.dropbox.com/s_2D428973624E7FC84C7D69D11421DE762BEA6B6F3361231FCDCAE0425D14526F_1664885448372_Untitled.drawio+17.png" alt="Image from bigocheatsheet.com" width="600" height="400" loading="lazy"></p>
<p>The Big O chart above shows that O(1), which stands for constant time complexity, is the best. This implies that your algorithm processes only one statement without any iteration. Then there's O(log n), which is good, and others like it, as shown below:</p>
<ul>
<li><p><strong>O(1)</strong> - Excellent/Best</p>
</li>
<li><p><strong>O(log n)</strong> - Good</p>
</li>
<li><p><strong>O(n)</strong> - Fair</p>
</li>
<li><p><strong>O(n log n)</strong> - Bad</p>
</li>
<li><p><strong>O(n^2)</strong>, <strong>O(2^n)</strong> and <strong>O(n!)</strong> - Horrible/Worst</p>
</li>
</ul>
<p>You now understand the various time complexities, and you can recognize the best, good, and fair ones, as well as the bad and worst ones (always avoid the bad and worst time complexity).</p>
<p>The next question that comes to mind is how you know which algorithm has which time complexity, given that this is meant to be a cheatsheet 😂.</p>
<ul>
<li><p>When your calculation is not dependent on the input size, it is a constant time complexity (O(1)).</p>
</li>
<li><p>When the input size is reduced by half, maybe when iterating, handling <a target="_blank" href="https://joelolawanle.com/posts/recursion-in-javascript-explained-for-beginners">recursion</a>, or whatsoever, it is a logarithmic time complexity (O(log n)).</p>
</li>
<li><p>When you have a single loop within your algorithm, it is linear time complexity (O(n)).</p>
</li>
<li><p>When you have nested loops within your algorithm, meaning a loop in a loop, it is quadratic time complexity (O(n^2)).</p>
</li>
<li><p>When the growth rate doubles with each addition to the input, it is exponential time complexity (O2^n).</p>
</li>
</ul>
<p>Let's begin by describing each time's complexity with examples. It's important to note that I'll use JavaScript in the examples in this guide, but the programming language isn't important as long as you understand the concept and each time complexity.</p>
<h2 id="heading-big-o-time-complexity-examples">Big O Time Complexity Examples</h2>
<h3 id="heading-constant-time-o1">Constant Time: O(1)</h3>
<p>When your algorithm is not dependent on the input size n, it is said to have a constant time complexity with order O(1). This means that the run time will always be the same regardless of the input size.</p>
<p>For example, if an algorithm is to return the first element of an array. Even if the array has 1 million elements, the time complexity will be constant if you use this approach:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> firstElement = <span class="hljs-function">(<span class="hljs-params">array</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> array[<span class="hljs-number">0</span>];
};

<span class="hljs-keyword">let</span> score = [<span class="hljs-number">12</span>, <span class="hljs-number">55</span>, <span class="hljs-number">67</span>, <span class="hljs-number">94</span>, <span class="hljs-number">22</span>];
<span class="hljs-built_in">console</span>.log(firstElement(score)); <span class="hljs-comment">// 12</span>
</code></pre>
<p>The function above will require only one execution step, meaning the function is in constant time with time complexity O(1).</p>
<p>But as I said earlier, there are various ways to achieve a solution in programming. Another programmer might decide to first loop through the array before returning the first element:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> firstElement = <span class="hljs-function">(<span class="hljs-params">array</span>) =&gt;</span> {
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; array.length; i++) {
    <span class="hljs-keyword">return</span> array[<span class="hljs-number">0</span>];
  }
};

<span class="hljs-keyword">let</span> score = [<span class="hljs-number">12</span>, <span class="hljs-number">55</span>, <span class="hljs-number">67</span>, <span class="hljs-number">94</span>, <span class="hljs-number">22</span>];
<span class="hljs-built_in">console</span>.log(firstElement(score)); <span class="hljs-comment">// 12</span>
</code></pre>
<p>This is just an example – likely nobody would do this. But if there is a loop, this is no longer constant time but now linear time with the time complexity O(n).</p>
<h3 id="heading-linear-time-on">Linear Time: O(n)</h3>
<p>You get linear time complexity when the running time of an algorithm increases linearly with the size of the input. This means that when a function has an iteration that iterates over an input size of n, it is said to have a time complexity of order O(n).</p>
<p>For example, if an algorithm is to return the factorial of any inputted number. This means if you input 5 then you are to loop through and multiply 1 by 2 by 3 by 4 and by 5 and then output 120:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> calcFactorial = <span class="hljs-function">(<span class="hljs-params">n</span>) =&gt;</span> {
  <span class="hljs-keyword">let</span> factorial = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">2</span>; i &lt;= n; i++) {
    factorial = factorial * i;
  }
  <span class="hljs-keyword">return</span> factorial;
};

<span class="hljs-built_in">console</span>.log(calcFactorial(<span class="hljs-number">5</span>)); <span class="hljs-comment">// 120</span>
</code></pre>
<p>The fact that the runtime depends on the input size means that the time complexity is linear with the order O(n).</p>
<h3 id="heading-logarithm-time-olog-n">Logarithm Time: O(log n)</h3>
<p>This is similar to linear time complexity, except that the runtime does not depend on the input size but rather on half the input size. When the input size decreases on each iteration or step, an algorithm is said to have logarithmic time complexity.</p>
<p>This method is the second best because your program runs for half the input size rather than the full size. After all, the input size decreases with each iteration.</p>
<p>A great example is binary search functions, which divide your sorted array based on the target value.</p>
<p>For example, suppose you use a binary search algorithm to find the index of a given element in an array:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> binarySearch = <span class="hljs-function">(<span class="hljs-params">array, target</span>) =&gt;</span> {
  <span class="hljs-keyword">let</span> firstIndex = <span class="hljs-number">0</span>;
  <span class="hljs-keyword">let</span> lastIndex = array.length - <span class="hljs-number">1</span>;
  <span class="hljs-keyword">while</span> (firstIndex &lt;= lastIndex) {
    <span class="hljs-keyword">let</span> middleIndex = <span class="hljs-built_in">Math</span>.floor((firstIndex + lastIndex) / <span class="hljs-number">2</span>);

    <span class="hljs-keyword">if</span> (array[middleIndex] === target) {
      <span class="hljs-keyword">return</span> middleIndex;
    }

    <span class="hljs-keyword">if</span> (array[middleIndex] &gt; target) {
      lastIndex = middleIndex - <span class="hljs-number">1</span>;
    } <span class="hljs-keyword">else</span> {
      firstIndex = middleIndex + <span class="hljs-number">1</span>;
    }
  }
  <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
};

<span class="hljs-keyword">let</span> score = [<span class="hljs-number">12</span>, <span class="hljs-number">22</span>, <span class="hljs-number">45</span>, <span class="hljs-number">67</span>, <span class="hljs-number">96</span>];
<span class="hljs-built_in">console</span>.log(binarySearch(score, <span class="hljs-number">96</span>));
</code></pre>
<p>In the code above, since it is a binary search, you first get the middle index of your array, compare it to the target value, and return the middle index if it is equal. Otherwise, you must check if the target value is greater or less than the middle value to adjust the first and last index, reducing the input size by half.</p>
<p>Because for every iteration the input size reduces by half, the time complexity is logarithmic with the order O(log n).</p>
<h3 id="heading-quadratic-time-on2">Quadratic Time: O(n^2)</h3>
<p>When you perform nested iteration, meaning having a loop in a loop, the time complexity is quadratic, which is horrible.</p>
<p>A perfect way to explain this would be if you have an array with n items. The outer loop will run n times, and the inner loop will run n times for each iteration of the outer loop, which will give total n^2 prints. If the array has ten items, ten will print 100 times (10^2).</p>
<p>Here is an example by <a target="_blank" href="https://jarednielsen.com/big-o-quadratic-time-complexity/">Jared Nielsen</a>, where you compare each element in an array to output the index when two elements are similar:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> matchElements = <span class="hljs-function">(<span class="hljs-params">array</span>) =&gt;</span> {
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; array.length; i++) {
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> j = <span class="hljs-number">0</span>; j &lt; array.length; j++) {
      <span class="hljs-keyword">if</span> (i !== j &amp;&amp; array[i] === array[j]) {
        <span class="hljs-keyword">return</span> <span class="hljs-string">`Match found at <span class="hljs-subst">${i}</span> and <span class="hljs-subst">${j}</span>`</span>;
      }
    }
  }
  <span class="hljs-keyword">return</span> <span class="hljs-string">"No matches found 😞"</span>;
};

<span class="hljs-keyword">const</span> fruit = [<span class="hljs-string">"🍓"</span>, <span class="hljs-string">"🍐"</span>, <span class="hljs-string">"🍊"</span>, <span class="hljs-string">"🍌"</span>, <span class="hljs-string">"🍍"</span>, <span class="hljs-string">"🍑"</span>, <span class="hljs-string">"🍎"</span>, <span class="hljs-string">"🍈"</span>, <span class="hljs-string">"🍊"</span>, <span class="hljs-string">"🍇"</span>];
<span class="hljs-built_in">console</span>.log(matchElements(fruit)); <span class="hljs-comment">// "Match found at 2 and 8"</span>
</code></pre>
<p>In the example above, there is a nested loop, meaning that the time complexity is quadratic with the order O(n^2).</p>
<h3 id="heading-exponential-time-o2n">Exponential Time: O(2^n)</h3>
<p>You get exponential time complexity when the growth rate doubles with each addition to the input (n), often iterating through all subsets of the input elements. Any time an input unit increases by 1, the number of operations executed is doubled.</p>
<p>The recursive Fibonacci sequence is a good example. Assume you're given a number and want to find the nth element of the Fibonacci sequence.</p>
<p>The Fibonacci sequence is a mathematical sequence in which each number is the sum of the two preceding numbers, where 0 and 1 are the first two numbers. The third number in the sequence is 1, the fourth is 2, the fifth is 3, and so on... (0, 1, 1, 2, 3, 5, 8, 13, …).</p>
<p>This means that if you pass in 6, then the 6th element in the Fibonacci sequence would be 8:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> recursiveFibonacci = <span class="hljs-function">(<span class="hljs-params">n</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (n &lt; <span class="hljs-number">2</span>) {
    <span class="hljs-keyword">return</span> n;
  }
  <span class="hljs-keyword">return</span> recursiveFibonacci(n - <span class="hljs-number">1</span>) + recursiveFibonacci(n - <span class="hljs-number">2</span>);
};

<span class="hljs-built_in">console</span>.log(recursiveFibonacci(<span class="hljs-number">6</span>)); <span class="hljs-comment">// 8</span>
</code></pre>
<p>In the code above, the algorithm specifies a growth rate that doubles every time the input data set is added. This means the time complexity is exponential with an order O(2^n).</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>In this guide, you have learned what time complexity is all about, how performance is determined using the Big O notation, and the various time complexities that exists with examples.</p>
<p>You can learn more via freeCodeCamp's <a target="_blank" href="https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/">JavaScript Algorithms and Data Structures curriculum</a>.</p>
<p>Happy learning!</p>
<p>You can access over 200 of my articles by <a target="_blank" href="https://joelolawanle.com/contents">visiting my website</a>. You can also use the search field to see if I've written a specific article.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Search Algorithms – Linear Search and Binary Search Code Implementation and Complexity Analysis ]]>
                </title>
                <description>
                    <![CDATA[ Search algorithms are a fundamental computer science concept that you should understand as a developer. They work by using a step-by-step method to locate specific data among a collection of data. In this article, we'll learn how search algorithms wo... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/search-algorithms-linear-and-binary-search-explained/</link>
                <guid isPermaLink="false">66ba0ebe9065919bb4e84ca5</guid>
                
                    <category>
                        <![CDATA[ algorithms ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #big o notation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ binary search ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Computer Science ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ashutosh Krishna ]]>
                </dc:creator>
                <pubDate>Tue, 11 Jan 2022 17:21:54 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/01/searching.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><strong>Search algorithms</strong> are a fundamental computer science concept that you should understand as a developer. They work by using a step-by-step method to locate specific data among a collection of data.</p>
<p>In this article, we'll learn how search algorithms work by looking at their implementations in Java and Python.</p>
<h2 id="heading-what-is-a-search-algorithm">What is a Search Algorithm?</h2>
<p>According to Wikipedia, a search algorithm is:</p>
<blockquote>
<p><em>Any algorithm which solves the search problem, namely, to retrieve information stored within some data structure, or calculated in the search space of a problem domain, either with discrete or continuous values.</em></p>
</blockquote>
<p>Search algorithms are designed to check or retrieve an element from any data structure where that element is being stored. They search for a target (key) in the search space.</p>
<h2 id="heading-types-of-search-algorithms">Types of Search Algorithms</h2>
<p>In this post, we are going to discuss two important types of search algorithms:</p>
<ol>
<li><p><strong>Linear or Sequential Search</strong></p>
</li>
<li><p><strong>Binary Search</strong></p>
</li>
</ol>
<p>Let's discuss these two in detail with examples, code implementations, and time complexity analysis.</p>
<h2 id="heading-linear-or-sequential-search">Linear or Sequential Search</h2>
<p>This algorithm works by sequentially iterating through the whole array or list from one end until the target element is found. If the element is found, it returns its index, else -1.</p>
<p>Now let's look at an example and try to understand how it works:</p>
<pre><code class="lang-python">arr = [<span class="hljs-number">2</span>, <span class="hljs-number">12</span>, <span class="hljs-number">15</span>, <span class="hljs-number">11</span>, <span class="hljs-number">7</span>, <span class="hljs-number">19</span>, <span class="hljs-number">45</span>]
</code></pre>
<p>Suppose the target element we want to search is <code>7</code>.</p>
<h3 id="heading-approach-for-linear-or-sequential-search">Approach for Linear or Sequential Search</h3>
<ul>
<li><p>Start with index 0 and compare each element with the target</p>
</li>
<li><p>If the target is found to be equal to the element, return its index</p>
</li>
<li><p>If the target is not found, return -1</p>
</li>
</ul>
<h3 id="heading-code-implementation"><strong>Code Implementation</strong></h3>
<p>Let's now look at how we'd implement this type of search algorithm in a couple different programming languages.</p>
<h4 id="heading-linear-or-sequential-search-in-java">Linear or Sequential Search in Java</h4>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> algorithms.searching;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LinearSearch</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        <span class="hljs-keyword">int</span>[] nums = {<span class="hljs-number">2</span>, <span class="hljs-number">12</span>, <span class="hljs-number">15</span>, <span class="hljs-number">11</span>, <span class="hljs-number">7</span>, <span class="hljs-number">19</span>, <span class="hljs-number">45</span>};
        <span class="hljs-keyword">int</span> target = <span class="hljs-number">7</span>;
        System.out.println(search(nums, target));

    }

    <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> <span class="hljs-title">search</span><span class="hljs-params">(<span class="hljs-keyword">int</span>[] nums, <span class="hljs-keyword">int</span> target)</span> </span>{
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> index = <span class="hljs-number">0</span>; index &lt; nums.length; index++) {
            <span class="hljs-keyword">if</span> (nums[index] == target) {
                <span class="hljs-keyword">return</span> index;
            }
        }
        <span class="hljs-keyword">return</span> -<span class="hljs-number">1</span>;
    }
}
</code></pre>
<h4 id="heading-linear-or-sequential-search-in-python">Linear or Sequential Search in Python</h4>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">search</span>(<span class="hljs-params">nums, target</span>):</span>
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(nums)):
        <span class="hljs-keyword">if</span> nums[i] == target:
            <span class="hljs-keyword">return</span> i
    <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
    nums = [<span class="hljs-number">2</span>, <span class="hljs-number">12</span>, <span class="hljs-number">15</span>, <span class="hljs-number">11</span>, <span class="hljs-number">7</span>, <span class="hljs-number">19</span>, <span class="hljs-number">45</span>]
    target = <span class="hljs-number">7</span>
    print(search(nums, target))
</code></pre>
<h3 id="heading-time-complexity-analysis"><strong>Time Complexity Analysis</strong></h3>
<p><strong>The Best Case</strong> occurs when the target element is the first element of the array. The number of comparisons, in this case, is 1. So, the time complexity is <code>O(1)</code>.</p>
<p><strong>The Average Case:</strong> On average, the target element will be somewhere in the middle of the array. The number of comparisons, in this case, will be N/2. So, the time complexity will be <code>O(N)</code> (the constant being ignored).</p>
<p><strong>The Worst Case</strong> occurs when the target element is the last element in the array or not in the array. In this case, we have to traverse the entire array, and so the number of comparisons will be N. So, the time complexity will be <code>O(N)</code>.</p>
<h2 id="heading-binary-search">Binary Search</h2>
<p>This type of searching algorithm is used to find the position of a specific value contained <strong>in a sorted array</strong>. The binary search algorithm works on the principle of divide and conquer and it is considered the best searching algorithm because it's faster to run.</p>
<p>Now let's take a sorted array as an example and try to understand how it works:</p>
<pre><code class="lang-python">arr = [<span class="hljs-number">2</span>, <span class="hljs-number">12</span>, <span class="hljs-number">15</span>, <span class="hljs-number">17</span>, <span class="hljs-number">27</span>, <span class="hljs-number">29</span>, <span class="hljs-number">45</span>]
</code></pre>
<p>Suppose the target element to be searched is <code>17</code>.</p>
<h3 id="heading-approach-for-binary-search"><strong>Approach for Binary Search</strong></h3>
<ul>
<li><p>Compare the target element with the middle element of the array.</p>
</li>
<li><p>If the target element is greater than the middle element, then the search continues in the right half.</p>
</li>
<li><p>Else if the target element is less than the middle value, the search continues in the left half.</p>
</li>
<li><p>This process is repeated until the middle element is equal to the target element, or the target element is not in the array</p>
</li>
<li><p>If the target element is found, its index is returned, else -1 is returned.</p>
</li>
</ul>
<h3 id="heading-code-implementation-1">Code Implementation</h3>
<h4 id="heading-binary-search-in-java">Binary Search in Java</h4>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> algorithms.searching;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BinarySearch</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        <span class="hljs-keyword">int</span>[] nums = {<span class="hljs-number">2</span>, <span class="hljs-number">12</span>, <span class="hljs-number">15</span>, <span class="hljs-number">17</span>, <span class="hljs-number">27</span>, <span class="hljs-number">29</span>, <span class="hljs-number">45</span>};
        <span class="hljs-keyword">int</span> target = <span class="hljs-number">17</span>;
        System.out.println(search(nums, target));
    }

    <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> <span class="hljs-title">search</span><span class="hljs-params">(<span class="hljs-keyword">int</span>[] nums, <span class="hljs-keyword">int</span> target)</span> </span>{
        <span class="hljs-keyword">int</span> start = <span class="hljs-number">0</span>;
        <span class="hljs-keyword">int</span> end = nums.length - <span class="hljs-number">1</span>;

        <span class="hljs-keyword">while</span> (start &lt;= end) {
            <span class="hljs-keyword">int</span> mid = start + (end - start) / <span class="hljs-number">2</span>;

            <span class="hljs-keyword">if</span> (nums[mid] &gt; target)
                end = mid - <span class="hljs-number">1</span>;
            <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (nums[mid] &lt; target)
                start = mid + <span class="hljs-number">1</span>;
            <span class="hljs-keyword">else</span>
                <span class="hljs-keyword">return</span> mid;
        }
        <span class="hljs-keyword">return</span> -<span class="hljs-number">1</span>;
    }
}
</code></pre>
<h4 id="heading-binary-search-in-python">Binary Search in Python</h4>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">search</span>(<span class="hljs-params">nums, target</span>):</span>
    start = <span class="hljs-number">0</span>
    end = len(nums)<span class="hljs-number">-1</span>

    <span class="hljs-keyword">while</span> start &lt;= end:
        mid = start + (end-start)//<span class="hljs-number">2</span>


        <span class="hljs-keyword">if</span> nums[mid] &gt; target:
            end = mid<span class="hljs-number">-1</span>
        <span class="hljs-keyword">elif</span> nums[mid] &lt; target:
            start = mid+<span class="hljs-number">1</span>
        <span class="hljs-keyword">else</span>:
            <span class="hljs-keyword">return</span> mid

    <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
    nums = [<span class="hljs-number">2</span>, <span class="hljs-number">12</span>, <span class="hljs-number">15</span>, <span class="hljs-number">17</span>, <span class="hljs-number">27</span>, <span class="hljs-number">29</span>, <span class="hljs-number">45</span>]
    target = <span class="hljs-number">17</span>
    print(search(nums, target))
</code></pre>
<h3 id="heading-time-complexity-analysis-1"><strong>Time Complexity Analysis</strong></h3>
<p><strong>The Best Case</strong> occurs when the target element is the middle element of the array. The number of comparisons, in this case, is 1. So, the time complexity is <code>O(1)</code>.</p>
<p><strong>The Average Case:</strong> On average, the target element will be somewhere in the array. So, the time complexity will be <code>O(logN)</code>.</p>
<p><strong>The Worst Case</strong> occurs when the target element is not in the list or it is away from the middle element. So, the time complexity will be <code>O(logN)</code>.</p>
<h3 id="heading-how-to-calculate-time-complexity">How to Calculate Time Complexity:</h3>
<p>Let's say the iteration in Binary Search terminates after <strong>k</strong> iterations.</p>
<p>At each iteration, the array is divided by half. So let’s say the length of array at any iteration is <strong>N</strong>.</p>
<p>At <strong>Iteration 1,</strong></p>
<pre><code class="lang-markdown">Length of array = N
</code></pre>
<p>At <strong>Iteration 2</strong>,</p>
<pre><code class="lang-markdown">Length of array = N/2
</code></pre>
<p>At <strong>Iteration 3</strong>,</p>
<pre><code class="lang-markdown">Length of array = (N/2)/2 = N/2^2
</code></pre>
<p>At <strong>Iteration k</strong>,</p>
<pre><code class="lang-markdown">Length of array = N/2^k
</code></pre>
<p>Also, we know that after k divisions, the <strong>length of the array becomes 1</strong>: Length of array = <strong>N⁄<sub>2</sub><sup>k</sup> = 1</strong>\=&gt; <strong>N = 2<sup>k</sup></strong></p>
<p>If we apply a log function on both sides: <strong>log<sub>2</sub> (N) = log<sub>2</sub> (2<sup>k</sup>)</strong>\=&gt; <strong>log<sub>2</sub> (N) = k log<sub>2</sub> (2)</strong></p>
<p>As <strong>(log<sub>a</sub> (a) = 1)</strong><br>Therefore,=&gt; <strong>k = log<sub>2</sub> (N)</strong></p>
<p>So now we can see why the time complexity of Binary Search is log<sub>2</sub> (N).</p>
<p>You can also visualize the above two algorithms using the simple tool built by <a target="_blank" href="https://www.linkedin.com/in/dipesh-patil/">Dipesh Patil</a> - <a target="_blank" href="https://dipeshpatil.github.io/algorithms-visualiser/#/searching">Algorithms Visualizer</a>.</p>
<h2 id="heading-order-agnostic-binary-search">Order-agnostic Binary Search</h2>
<p>Suppose, we have to find a target element in a sorted array. We know that the array is sorted, but we don’t know if it’s sorted in ascending or descending order.</p>
<h3 id="heading-approach-for-order-agnostic-binary-search"><strong>Approach for Order-agnostic Binary Search</strong></h3>
<p>The implementation is similar to binary search except that we need to identify whether the array is sorted in ascending order or descending order. This then lets us make the decision about whether to continue the search in the left half of the array or the right half of the array.</p>
<ul>
<li><p>We first compare the target with the middle element</p>
</li>
<li><p>If the array is sorted in ascending order and the target is less than the middle element <strong>OR</strong> the array is sorted in descending order and the target is greater than the middle element, then we continue the search in the lower half of the array by setting <code>end=mid-1</code>.</p>
</li>
<li><p>Otherwise, we perform the search in the upper half of the array by setting <code>start=mid+1</code></p>
</li>
</ul>
<p>The only thing we need to do is to figure out whether the array is sorted in ascending order or descending order. We can easily find this by comparing the first and last elements of the array.</p>
<pre><code class="lang-markdown">if arr[0] <span class="xml"><span class="hljs-tag">&lt; <span class="hljs-attr">arr</span>[<span class="hljs-attr">arr.length-1</span>]
    <span class="hljs-attr">array</span> <span class="hljs-attr">is</span> <span class="hljs-attr">sorted</span> <span class="hljs-attr">in</span> <span class="hljs-attr">ascending</span> <span class="hljs-attr">order</span> 
<span class="hljs-attr">else</span>
    <span class="hljs-attr">array</span> <span class="hljs-attr">is</span> <span class="hljs-attr">sorted</span> <span class="hljs-attr">in</span> <span class="hljs-attr">descending</span> <span class="hljs-attr">order</span></span></span>
</code></pre>
<h3 id="heading-code-implementation-2"><strong>Code Implementation</strong></h3>
<h4 id="heading-order-agnostic-binary-search-in-java">Order-agnostic Binary Search in Java</h4>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> algorithms.searching;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderAgnosticBinarySearch</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        <span class="hljs-keyword">int</span>[] nums1 = {-<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">4</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>, <span class="hljs-number">12</span>, <span class="hljs-number">15</span>, <span class="hljs-number">19</span>, <span class="hljs-number">32</span>, <span class="hljs-number">45</span>, <span class="hljs-number">67</span>, <span class="hljs-number">99</span>};
        <span class="hljs-keyword">int</span>[] nums2 = {<span class="hljs-number">99</span>, <span class="hljs-number">67</span>, <span class="hljs-number">45</span>, <span class="hljs-number">32</span>, <span class="hljs-number">19</span>, <span class="hljs-number">15</span>, <span class="hljs-number">12</span>, <span class="hljs-number">8</span>, <span class="hljs-number">7</span>, <span class="hljs-number">6</span>, <span class="hljs-number">4</span>, <span class="hljs-number">2</span>, -<span class="hljs-number">1</span>};
        <span class="hljs-keyword">int</span> target = -<span class="hljs-number">1</span>;
        System.out.println(search(nums1, target));
        System.out.println(search(nums2, target));
    }

    <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> <span class="hljs-title">search</span><span class="hljs-params">(<span class="hljs-keyword">int</span>[] arr, <span class="hljs-keyword">int</span> target)</span> </span>{
        <span class="hljs-keyword">int</span> start = <span class="hljs-number">0</span>;
        <span class="hljs-keyword">int</span> end = arr.length - <span class="hljs-number">1</span>;

        <span class="hljs-keyword">boolean</span> isAscending = arr[start] &lt; arr[end];

        <span class="hljs-keyword">while</span> (start &lt;= end) {
            <span class="hljs-keyword">int</span> mid = start + (end - start) / <span class="hljs-number">2</span>;

            <span class="hljs-keyword">if</span> (target == arr[mid])
                <span class="hljs-keyword">return</span> mid;

            <span class="hljs-keyword">if</span> (isAscending) {
                <span class="hljs-keyword">if</span> (target &lt; arr[mid]) {
                    end = mid - <span class="hljs-number">1</span>;
                } <span class="hljs-keyword">else</span> {
                    start = mid + <span class="hljs-number">1</span>;
                }
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">if</span> (target &lt; arr[mid]) {
                    start = mid + <span class="hljs-number">1</span>;
                } <span class="hljs-keyword">else</span> {
                    end = mid - <span class="hljs-number">1</span>;
                }
            }
        }
        <span class="hljs-keyword">return</span> -<span class="hljs-number">1</span>;
    }


}
</code></pre>
<h4 id="heading-order-agnostic-binary-search-in-python">Order-agnostic Binary Search in Python</h4>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">search</span>(<span class="hljs-params">nums, target</span>):</span>
    start = <span class="hljs-number">0</span>
    end = len(nums)<span class="hljs-number">-1</span>

    is_ascending = nums[start] &lt; nums[end]

    <span class="hljs-keyword">while</span> start &lt;= end:
        mid = start + (end-start)//<span class="hljs-number">2</span>

        <span class="hljs-keyword">if</span> target == nums[mid]:
            <span class="hljs-keyword">return</span> mid

        <span class="hljs-keyword">if</span> is_ascending:
            <span class="hljs-keyword">if</span> target &lt; nums[mid]:
                end = mid<span class="hljs-number">-1</span>
            <span class="hljs-keyword">else</span>:
                start = mid+<span class="hljs-number">1</span>
        <span class="hljs-keyword">else</span>:
            <span class="hljs-keyword">if</span> target &lt; nums[mid]:
                start = mid+<span class="hljs-number">1</span>
            <span class="hljs-keyword">else</span>:
                end = mid<span class="hljs-number">-1</span>

    <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
    nums1 = [<span class="hljs-number">-1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">4</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>, <span class="hljs-number">12</span>, <span class="hljs-number">15</span>, <span class="hljs-number">19</span>, <span class="hljs-number">32</span>, <span class="hljs-number">45</span>, <span class="hljs-number">67</span>, <span class="hljs-number">99</span>]
    nums2 = [<span class="hljs-number">99</span>, <span class="hljs-number">67</span>, <span class="hljs-number">45</span>, <span class="hljs-number">32</span>, <span class="hljs-number">19</span>, <span class="hljs-number">15</span>, <span class="hljs-number">12</span>, <span class="hljs-number">8</span>, <span class="hljs-number">7</span>, <span class="hljs-number">6</span>, <span class="hljs-number">4</span>, <span class="hljs-number">2</span>, <span class="hljs-number">-1</span>]
    target = <span class="hljs-number">-1</span>
    print(search(nums1, target))
    print(search(nums2, target))
</code></pre>
<h3 id="heading-time-complexity-analysis-2"><strong>Time Complexity Analysis</strong></h3>
<p>There will be no change in the time complexity, so it will be the same as Binary Search.</p>
<h1 id="heading-conclusion"><strong>Conclusion</strong></h1>
<p>In this article, we discussed two of the most important search algorithms along with their code implementations in Python and Java. We also looked at their time complexity analysis.</p>
<p>Thanks for reading!</p>
<p><a target="_blank" href="https://newsletter.ashutoshkrris.tk">Subscribe to my newsletter</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Time Complexity – Big O Notation Course ]]>
                </title>
                <description>
                    <![CDATA[ Big O notation is an important tools for computer scientists to analyze the cost of an algorithm. Most software engineers should have an understanding of it. We just published a course on the freeCodeCamp.org YouTube channel that will help you unders... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-big-o-notation/</link>
                <guid isPermaLink="false">66b20401a8b92c9329236484</guid>
                
                    <category>
                        <![CDATA[ #big o notation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Tue, 10 Aug 2021 13:12:34 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/08/BIGO2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Big O notation is an important tools for computer scientists to analyze the cost of an algorithm. Most software engineers should have an understanding of it.</p>
<p>We just published a course on the freeCodeCamp.org YouTube channel that will help you understand Big O Notation.</p>
<p>Georgio Tunson, aka selikapro, created this course. He does a great job explaining the complex concepts with diagrams and code.</p>
<p>Big O notation is a way to describe how long an algorithm takes to run or how much memory is used by an algorithm.</p>
<p>Here are the sections covered in this course:</p>
<ul>
<li>What Is Big O?</li>
<li>O(n^2) Explanation</li>
<li>O(n^3) Explanation</li>
<li>O(log n) Explanation Recursive</li>
<li>O(log n) Explanation Iterative</li>
<li>O(log n) What Is Binary Search?</li>
<li>O(log n) Coding Binary Search</li>
<li>O(n log n) Explanation</li>
<li>O(n log n) Coding Merge Sort</li>
<li>O(n log n) Merge Sort Complexity Deep Dive</li>
<li>O(2^n) Explanation With Fibonacci</li>
<li>O(n!) Explanation</li>
<li>Space Complexity &amp; Common Mistakes</li>
</ul>
<p>Watch the full course below or on <a target="_blank" href="https://www.youtube.com/watch?v=Mo4vesaut8g">the freeCodeCamp.org YouTube channel</a> (2-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/Mo4vesaut8g" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-transcript">Transcript</h2>
<p>(autogenerated)</p>
<p>Big O notation is used to classify algorithms based on how fast they grow or decline.</p>
<p>It is very important to understand for many types of programming, Giorgio Thompson does a great job of breaking down big O notation in this course.</p>
<p>Hey, what's up everybody and welcome to my mini series on big O notation.</p>
<p>And this mini series, you'll learn everything that you need to know about big O notation and how you can use it to improve your ability to create efficient algorithms.</p>
<p>I'll use whiteboard illustrations to help you visualize and understand concepts followed by coding tutorials that you can follow along with to further solidify your grasp of the concepts will answer the question what is big O notation? And why is it useful? So what is big O notation? Big O notation is used to analyze the efficiency of an algorithm as its input approaches infinity, which means that as the size of the input to the algorithm grows, how drastically do the space or time requirements grow with it.</p>
<p>For example, let's say that we have a dentist and she takes 30 minutes to treat one patient.</p>
<p>As her line of patients increases, the time that it takes for her to treat all of the patients will scale linearly with the number of patients waiting in line.</p>
<p>This is because it always takes her a constant amount of time to treat each individual patient which is 30 minutes.</p>
<p>This gives us a general understanding of how long our dentist would take to treat 10 patients 20 patients or even 100,000 patients.</p>
<p>This is because since we know that the dentist takes a constant amount of time, which is 30 minutes to treat each patient, we can always calculate the time it would take for her to treat any number of patients by multiplying the number of patients times 30 minutes.</p>
<p>With this in mind, we can categorize her efficiency as being linear.</p>
<p>Or as we would say in Big O terms big O of n, where n is equal to the number of patients the time that it takes for her to finish her work scales linearly or proportionally with the number of patients, we use the same technique to determine the efficiency of algorithms, we can get a general idea of how functions time efficiency scales by categorizing a given functions efficiency the same way that we categorize the dentist's efficiency.</p>
<p>Let's create an easily comprehensible function that scales similarly to the dentist.</p>
<p>So this function is in the same linear category as our dentist, let's step through it and find out why.</p>
<p>To start the input to our function is an array with seven items inside of it.</p>
<p>For each of those items, we will log this expression which multiplies 1000 times 100,000.</p>
<p>Now don't let these large numbers for you, it will always take the same amount of time to multiply 1000 times 100,000.</p>
<p>Therefore this line of code takes constant time.</p>
<p>Which brings me to a very important point, when considering the efficiency of a function.</p>
<p>These lines that take constant time do not matter.</p>
<p>Well, at least for our purposes, they don't.</p>
<p>This is because if our array were some crazy length, like 200 million, changing this expression to something simpler, like one plus one would have a negligible effect on the efficiency of the function as a whole, we'd still need to iterate through 200 million items in an array.</p>
<p>In fact, even if the function looked like this, we would still ignore all of these constants and say that this function scales linearly or is big O of n.</p>
<p>Similarly, if we think back to our dentist example, we see that she took 30 minutes per patient.</p>
<p>But even if she took three hours per patient, the amount of time it takes her to see all of her patients will still scale linearly.</p>
<p>This can be difficult to grasp at first, but it starts to make sense over time.</p>
<p>So in the last slide, there was a lot of talk about ignoring the constants.</p>
<p>But what exactly is a constant? A constant is any step that doesn't scale with the input to the function.</p>
<p>For example, the time to evaluate this expression does not change with the input because both 101,000 are constants.</p>
<p>That is, these values are always the same, this expression always results in the same value.</p>
<p>And it always takes the same amount of time or constant time to return the same result.</p>
<p>Just like we use big O of n to describe linear functions.</p>
<p>We also have a big O name for constant algorithms, which is big O of one.</p>
<p>A good way to think about it is every line of code is actually a function in and of itself, which is actually true.</p>
<p>For example, let's reintroduce this function.</p>
<p>So this line of code is the reason why the entire linear func function is O of n because as you can see as the size of n increases the number of iterations that the for loop must traverse increases as well.</p>
<p>But let's take this second line into consideration.</p>
<p>Let's for one second pretend that we have a function that contains only this line.</p>
<p>Now as you can see, with this function, we pass in an array, but the function does nothing with the array.</p>
<p>The only operation within the function is constant because it doesn't scale with any input.</p>
<p>So regardless of how large of an array is passed to this function, This line always produces the same result.</p>
<p>And this is the only line in the function.</p>
<p>So therefore, this entire function is over one.</p>
<p>But wait.</p>
<p>In this function, we have multiple lines that are over one yet we still prioritize the line that is O of n and ignore the O of one operations.</p>
<p>Why is this? Well, this brings us to our last important note, in bego, we have a growth hierarchy, which looks something like this.</p>
<p>Now, don't panic, you don't need to understand all of these just yet.</p>
<p>So let's only pay attention to the ones relevant to this video, or even an old one.</p>
<p>We'll learn about the other ones in following videos for this series.</p>
<p>This chart shows the efficiency categories in order from good to bad.</p>
<p>That is to say that this first case of one is the best case.</p>
<p>And this last one is the worst case.</p>
<p>In big O notation, when determining the efficiency of an algorithm, we only care about the worst case.</p>
<p>So that means that the worst case where the highest order operation trumps the operations that have better performance.</p>
<p>So if we add the performance of all of these lines up, like so all of the lines of code that are o of one get cancelled out because oh Vin is the worst performing or highest order part of the function.</p>
<p>And this, ladies and gentlemen, is why we ignore constants, because we're actually just eliminating the non dominant items.</p>
<p>Because as a functions, input moves towards infinity, constants become less and less significant.</p>
<p>So to recap, when evaluating an algorithms efficiency, we must take into consideration the efficiency of each step within the algorithm, we then find the highest order step, or the step that has the worst performance, and prioritize it over all of the better performing steps, steps that are constant, or that are over one or as good as it gets in terms of efficiency.</p>
<p>So we always ignore them, unless the entirety of the function is constant, or o of one.</p>
<p>And in that case, we would categorize the entire function as constant or o of one.</p>
<p>And that Ladies and gentlemen, is your answer to what is big O notation.</p>
<p>Okay, so to understand O of n square, we're going to need to take the function into consideration in the function will look something like this.</p>
<p>So what this function is doing is it's going to take in a number in and it's going to iterate through this for loop starting with the number zero all the way up until the number in and for every iteration of this top for loop, we're also going to loop through this nested for loop.</p>
<p>And this nested for loop is doing the exact same thing that this for loop is doing.</p>
<p>It's iterating through every number, starting with zero up until the number in and within this nested for loop, we're console logging the coordinates for a sell within a matrix.</p>
<p>But to make things clear, instead of illustrating a console log of the index i and j, I'm just going to draw a square where these coordinates should be for every iteration of this nested for loop.</p>
<p>So if it sounds confusing, just try to bear with me, I promise it'll become clear.</p>
<p>Okay, so let's say for example, that we call the square function with the number four.</p>
<p>So that means that we're going to iterate through this top for loop starting with starting from zero, and then we're going to iterate all the way up until I is no longer less than four, once I becomes equal to four, then we will stop the iteration.</p>
<p>And then that's only for this top for the for each iteration of this actual for loop, then we're going to loop through the entirety of this nested for loop and do this console log.</p>
<p>And instead of logging the coordinates, like I said, we'll draw a square where the coordinates would be, so you guys can visualize this better.</p>
<p>So let's go ahead and get started.</p>
<p>So for the first iteration, is going to equal zero, and then we move on into this nested for loop.</p>
<p>And then we're going to iterate through the entirety of this nested for loop.</p>
<p>So right now i and j are zero, so i and j are both zero.</p>
<p>So we're currently at the first iteration of this for loop in the first iteration of this for loop, and we'll draw a square and then we move up one iteration of this for loop, so j becomes one and we'll draw another square.</p>
<p>And then j becomes two, it will draw another square and then j becomes three, and we'll draw another square and now j is four.</p>
<p>And since j is four, that means that j is no longer less than n, because n is four, and j is four, and n is four.</p>
<p>So j, and n are now equal, so will no longer iterate through this for loop.</p>
<p>So now we go back up to this for loop.</p>
<p>And now I is equal to one, so i is equal to one, and j is equal to zero, so we'll draw a square and then i is equal to one is in j is equal to one so we'll draw a square and then i is equal to one and j is equal to two.</p>
<p>So draw a square and is equal to one and j is equal to three, it will draw a square.</p>
<p>And now back to this top for loop again, because j and n are now equal, we're back up to this top for loop again, now i is equal to two, so i is equal to two, and j is equal to zero, so we'll draw a square and then i is equal to two, and j is equal to one, so we'll draw a square and i is equal to two, and j is equal to two.</p>
<p>So we'll draw a square and i is equal to two, and j is equal to three, so we'll draw a square and then now j is equal to four, which is our in, so will no longer iterate through this nested for loop, and we move back up to the top of this for loop, now, i is equal to three, i is equal to three at this point, and j is equal to zero again, so we'll draw square is equal to three and j is equal to one.</p>
<p>So we'll draw a square j is equal to two, j is equal to three.</p>
<p>And then j is equal to four.</p>
<p>So we no longer iterate through this nested for loop.</p>
<p>And at this point, our i is now equal to four and our n is also equal to four.</p>
<p>And we only iterate through this top for loop as long as r is less than in but our eyes now equal to our in.</p>
<p>So now we stop iterating through this top four loop.</p>
<p>And what we're left with is this matrix here.</p>
<p>And the reason why I said that these are coordinates for cells within a matrix is because this here is a matrix.</p>
<p>And these are rows.</p>
<p>And these are columns.</p>
<p>So we can look at AI, as being our column.</p>
<p>And then we can look at j is being row.</p>
<p>So for each iteration 0123 of our column, we also have an iteration for row 0123.</p>
<p>So coordinates being zero, and zero, were the coordinates for this square, and then zero and one are the coordinates for this square, and zero, and two are the coordinates for this square, and so on, and so forth.</p>
<p>So what does all this have to do with oben square? Well, hey, just one quick interruption.</p>
<p>If you are finding this video helpful, or it's bringing you to some type of understanding, please take the time to like and subscribe.</p>
<p>If we think about it, this is a square matrix, that is each side will be of the same length.</p>
<p>And by length, I mean 1234.</p>
<p>This is of length four, and 1234.</p>
<p>And this is of length four, and to find the area of a square, we just need to multiply the length of one side by itself, because every side of a square is of the same length.</p>
<p>So if this were a rectangle, we would multiply the width times the height, but for square, we can just multiply by itself because the width and the height will be the same length.</p>
<p>So to get the area of this square, we're just going to multiply four times four, four times four.</p>
<p>And that's going to equal the number of cells within this matrix, which also happens to be the number of times that we had to perform this code, which is four times four is 16.</p>
<p>And four times four is the same thing as four square.</p>
<p>So O of n square, our n is actually four.</p>
<p>And that is why typically functions with nested for loops, like a for loop and a for loop nested within it like this function is considered over in square.</p>
<p>I hope that makes sense.</p>
<p>Okay, so to understand all of in queue, let's take a function into consideration.</p>
<p>This cube function takes in an argument in which is a number.</p>
<p>And it's going to iterate through this for loop and for every iteration of this for loop is going to iterate through the entirety of this for loop.</p>
<p>And for every iteration of this for loop, we will need to iterate through the entirety of this for loop.</p>
<p>And I'm going to have to apologize ahead of time for my disappointing drawing skills.</p>
<p>But to illustrate this, I'm actually going to have to draw three dimensional shapes, which is not something that I'm entirely good at But anyways, for now, let's just ignore this image.</p>
<p>For now.</p>
<p>Let's focus on this function.</p>
<p>So for the top level for We're going to be iterating up until in.</p>
<p>So if we pass the number four to our cube function, we'll end up here at this first for loop.</p>
<p>And we're going to iterate starting from zero all the way up until n, which is four.</p>
<p>So let's get started.</p>
<p>So for our first iteration of this top level for loop, I is going to be zero.</p>
<p>Now for all the in cube, we're adding in additional nested for loop.</p>
<p>So there's no longer just a row and a column.</p>
<p>Now we have rows, columns.</p>
<p>And we also have this third dimension here, which we'll just call height.</p>
<p>So we have the columns that go in this direction, the rows that go in this direction, and the height that go in this direction.</p>
<p>So at this point, we're working with a three dimensional array, it's no longer a two dimensional array.</p>
<p>And it's the same concept.</p>
<p>So it's not as difficult as it seems, we're going to draw it out now.</p>
<p>So we would start with this initial for loop, and is going to start off as zero, right.</p>
<p>And we'll say that this initial for loop is representative of our columns.</p>
<p>So we can actually go ahead and write these numbers out just so you guys can see.</p>
<p>So we'll say that when I zero, this column is zero.</p>
<p>When I is one, we're talking about this column.</p>
<p>When I say two, we're talking about this column, and one is three, we're talking about this column.</p>
<p>And of course, once I becomes four, we're no longer going to iterate through this for loop because I is then no longer less than n, which is four, it will be equal to four.</p>
<p>And we can say the same thing for the rows.</p>
<p>So we would say that row zero would be here, row one would be here, row two would be here, and row three would be here.</p>
<p>Now I apologize if this is difficult for you to see, it's three dimensional.</p>
<p>So it's not really easy to draw this, but I hope that you guys can visualize what I'm trying to say.</p>
<p>And then the same thing for the height, the height would be represented by this for loop here.</p>
<p>So okay, and let's actually, I'm sorry, I should, I should actually just name these by the letter that we're using in the actual function.</p>
<p>So instead of calling this height, we'll call this K.</p>
<p>Because it's representative, this this for loop is represented as representing k here, so we'll just call this k as well.</p>
<p>And instead of calling this columns, we'll call it AI.</p>
<p>And instead of calling this rose, we'll call it J.</p>
<p>So for every iteration of this for loop, we're going to be moving up this k axis.</p>
<p>So if we were to write in what the index is for K, it's going to kind of be hard to see right now.</p>
<p>But it will be 012.</p>
<p>And three, so let's try to draw this out.</p>
<p>So let's do this step by step for a little bit to get you guys understanding what's happening.</p>
<p>So for this first iteration of this top level for loop, is going to be equal to zero.</p>
<p>So that means that I, this line, we're going to be here at the zero index of I.</p>
<p>And then for this nested for loop, J is also going to be equal to zero.</p>
<p>So j is here.</p>
<p>And we're going to be at zero here.</p>
<p>So we're still going to be here.</p>
<p>And for K as well, this for loop here.</p>
<p>This x is here, we're also going to be at zero, which is here.</p>
<p>So we're still going to be here.</p>
<p>So instead of console logging these coordinates, we'll just draw a square for this coordinate.</p>
<p>So this coordinate is 00, and zero, and 00, and zero, so we'll just draw a square here.</p>
<p>And again, you're going to have to excuse my poor square drawing prowess.</p>
<p>And since we continue with K until K is no longer less than n, we're going to continue to iterate through this for loop.</p>
<p>So to help you guys out, I can just tell, I can just write I right now is equal to zero, J right now is equal to zero and K.</p>
<p>It was equal to zero, but we just do the square for K at zero.</p>
<p>So now K is going to iterate it's going to increment one.</p>
<p>So K is now going to be equal to one.</p>
<p>So when k is at one, and j is at zero, and is at zero, so i j.</p>
<p>k is at one, then we're going to go up another square and this is going to be kind of hard to see because I'm literally trying to draw three dimensional space Whereas here and I'm just like terrible at drawing, I'll do my best.</p>
<p>Give that one more go.</p>
<p>Okay, so once we do that k increments one, so K is now two, two is here, and then i and j are still zero, so we're still going to be at j here.</p>
<p>So we're still going to be in this section.</p>
<p>So we'll draw another two dimensional square here.</p>
<p>I mean, two dimensional cube, excuse me, if I call a cube square, that's definitely not right cube.</p>
<p>So there's another cube.</p>
<p>And of course, K is going to increment again.</p>
<p>So then K is going to become three.</p>
<p>And then at three here, we'll drill another square, I mean, cube, sorry.</p>
<p>And at this point, K is going to increment.</p>
<p>And then it's going to be equal to four.</p>
<p>And once k is equal to 4k is no longer less than n, because n is also four.</p>
<p>So now k is equal to n.</p>
<p>So this world is done.</p>
<p>And now we move up to this for loop.</p>
<p>And this for loop will increment one and j will then be equal to one, because for each iteration of this for loop, we go through the entirety of this for loop.</p>
<p>So we've gone through the entirety of this for loop.</p>
<p>So now we can move up one iteration in this for loop.</p>
<p>And we can't move back up to this for loop until we iterate through everything within this for loop.</p>
<p>So we're still on so we're only incrementing the row and then we're going back into iterating K.</p>
<p>So now that j is equal to one is still zero, so we're still here, we're still here, because this is the column and then I still zero, this is I and I still had zero.</p>
<p>And we're still here, but j went from zero to one.</p>
<p>So now we're here.</p>
<p>So we're back into k, and k is going to start off zero again.</p>
<p>So at I being zero, J being one and K being zero, because zero, okay, this is K, and this is zero.</p>
<p>And we're here we'll draw a square.</p>
<p>Sorry, once again, I said square Yeah, we'll draw q 10k increments.</p>
<p>We'll draw another cube.</p>
<p>And then k increments, and we draw another cube.</p>
<p>And then k increments.</p>
<p>Individual one more Q, because once k reaches four, so yeah, K, okay, we'll increment one more time, and it'll reach four.</p>
<p>And now K is no longer less than in.</p>
<p>So we go back up to RJ for loop, and that will increment one.</p>
<p>So this one will now become two.</p>
<p>And it's pretty much the same thing.</p>
<p>Throughout the entire throughout the entire function, and it's getting hard to see the cubes that I'm drawing here.</p>
<p>But eventually, we'll get to a point where the entire cube is filled in, which will look something like this.</p>
<p>So we'll get to a point where the entirety of this cube would be filled with these miniature cubes, which are just the iterations of these four loops.</p>
<p>So once we get to that point, and thank you cube is completely filled in.</p>
<p>At that point, that means that we have iterated through the entirety of this top level for loop.</p>
<p>And feel free to take the time to try and draw this out on your own.</p>
<p>But I basically went through as much as I could with the time that I have in this video, but it's pretty much the same thing until the entirety of the cube is filled.</p>
<p>So once all these four loops have completed, you'll be left with the cube that looks like this.</p>
<p>And since this is a cube, that means that its height, and its length, and its width, are all going to be of the same length.</p>
<p>And that is to say that they're all going to be in because if you look here we went through in iterations of J.</p>
<p>We went through iterations of AI And we went through in iterations of K.</p>
<p>And again, I'm sorry, for my poor drawing, I hope that you get the idea.</p>
<p>So if n is four, this is going to be four, this is going to be four, and this is going to be four.</p>
<p>And to get the volume of this cube, to get the volume, the space within this cube, since we know that all of these are going to be the same, we only need to know one of them.</p>
<p>And one of them to get the volume we just do for this case for cube, and four cubed is 64.</p>
<p>And that will be the volume of this cube, which just means that there are 64 of those of these miniature cubes within this larger cube.</p>
<p>And that's the volume.</p>
<p>So O of n cubed, r n is four.</p>
<p>So o of four cube, which equals 64, which is the volume of this cube.</p>
<p>which also happens to be the number of times we would perform this function console log the coordinates, but in our case, we just do the squares.</p>
<p>And that is why this function is O of n cube must first understand what a logarithm is.</p>
<p>Simply put a logarithm is the power that a number needs to be raised to to get some other number.</p>
<p>I know that doesn't make much sense out of context, but don't worry, I've got you covered.</p>
<p>Let's take the number eight into consideration.</p>
<p>So we want to raise some number to some power to get a PhD.</p>
<p>In computer science unless specified otherwise, we can always assume that the number that we want to raise to sum power is two.</p>
<p>So let's rewrite this.</p>
<p>So we want to raise two to some power to get eight.</p>
<p>So this same equation can be written like this, or this two here is called the base.</p>
<p>And let's not forget that in computer science, the base is always two.</p>
<p>So to find the answer to this, we just need to find the answer to this.</p>
<p>With that in mind, we can see that if we raise two to the power of three, we get the number that we're looking for eight, so log base two of eight is three.</p>
<p>So with all of that in mind, let's move on to the meaning of Oh login.</p>
<p>For this portion, we will be using a very bare bones recursive function to visualize Oh login, but don't worry, I will walk you through every step.</p>
<p>Just stay with me.</p>
<p>So we will start with a number and we will use eight so that you can easily see how this relates to our explanation of logarithms in the previous slide.</p>
<p>So this variable in we will be passing to our recursive function that looks like this.</p>
<p>So this functions time complexity is Oh, log in, let's dig deeper to find out why.</p>
<p>For now, let's just ignore this first line and focus on what the function is actually doing.</p>
<p>So when we pass a number into this function, it divides in by two or splits it in half, and then calls itself with the new half or divided number.</p>
<p>Let's visualize this using the graph.</p>
<p>So we first call the function with the value eight, this eight is then divided by two.</p>
<p>The function then takes the result of the division and passes it recursively to itself as the new value for n which in turn results in us going one level deeper.</p>
<p>We then do the same thing with our new value for n which is four, that four is divided by two resulting in a new n.</p>
<p>And the function then passes our new value for n to a recursive call to itself again, resulting in us going one more level deeper.</p>
<p>We then do the same thing with our most recent value for n which is two, we divide it by two and the function once again recursively calls itself at this level we will stop is we can no longer divide in without getting fractions as the result.</p>
<p>Now we have arrived at the beginning of the secret to understanding Oh login, so watch closely.</p>
<p>If you look at our graph, you will see that we've gone one to three levels deep.</p>
<p>If you recall from our previous slide, the log base two of eight is three.</p>
<p>Our input in is eight and we've gone three levels deep.</p>
<p>You will also notice that we have to raise two to the power of three or multiply Two times two times two to get eight.</p>
<p>And since division is just the inverse of multiplication, we can see that when we do something like this.</p>
<p>So that means that this function has a time complexity of Oh, login.</p>
<p>Why? Because our n is eight.</p>
<p>And in computer science, our base is always two.</p>
<p>And we must have our n three times or go three levels deep in our recursive function to get to a point where we can no longer reasonably have our input in, which is another way of saying that log base two of eight equals three.</p>
<p>And that, ladies and gentlemen, is the secret to understanding Oh, login time complexity.</p>
<p>And as a quick note, this is not only applicable to recursive functions.</p>
<p>And if you're curious about that line of code that we covered up earlier, all it does is make sure we stopped dividing in when n becomes one or otherwise, the function would keep dividing fraction after fraction until we eventually exceed the maximum call stack.</p>
<p>So we'll start with a very simple function, which contains only a while loop that assigns a new value to the variable in for each iteration.</p>
<p>And for this example, let's imagine that we're passing the value eight two hour in for this function.</p>
<p>So that means we'll iterate through this while loop as long as eight is greater than one.</p>
<p>And for each iteration of this while loop, we're going to divide our n by two and reassign it to n.</p>
<p>So our n is going to be halved for each iteration.</p>
<p>So currently, our n is equal to eight, because we passed in eight as in and while n is greater than one, we're going to iterate.</p>
<p>So right now is eight, which is greater than one.</p>
<p>So we'll do this math dot floor n divided by two for our first iteration, which would set our n equal to eight divided by two, which is four.</p>
<p>And this math dot floor, all it does is it floors the result of our division.</p>
<p>So for example, if we have math dot floor, five divided by two, we would get two here, instead of 2.5.</p>
<p>So after this first iteration, our in is now equal to four.</p>
<p>So while n is greater than one, we're going to do another iteration.</p>
<p>So four is greater than one, so we're going to do this again.</p>
<p>And n is going to equal four divided by two, which is going to equal two.</p>
<p>So now our n is equal to two.</p>
<p>And while n is greater than one, we're going to do this again.</p>
<p>So in is currently greater than one, two is greater than one.</p>
<p>So we're going to do it again for third iteration.</p>
<p>So in is going to equal two divided by two, which is going to equal one.</p>
<p>So at this point, our n is equal to one.</p>
<p>And we're going to go to this condition here again.</p>
<p>So while in is greater than one, we're going to do this.</p>
<p>But right now n is equal to one, it's no longer greater than one.</p>
<p>So we're not going to continue with this while loop.</p>
<p>So why is this function of login, so our n is eight.</p>
<p>So that means that this function should be o of log eight.</p>
<p>And if you remember from the previous video on old login complexity, this is just the same thing as o of log base two, eight, which just means what power do we need to raise to buy to get eight.</p>
<p>And if we write this out, what power do we need to raise to buy to get eight, we see that we need to raise two to the third power to get eight, because two times two times two equals eight.</p>
<p>So this three is what's important, because division is just the inverse of multiplication.</p>
<p>So if we need to multiply two times two times two to get eight, then we should also be able to divide eight by two three times to get one.</p>
<p>So there's 123.</p>
<p>So that means that for this function, when we pass in a value for n, we're always going to need to divide this value in By to log in times before we can get one, which is just another way of saying that when we pass into this function, we're going to iterate through this while loop, log in iterations, before we get to the value one.</p>
<p>So if you see here we have one iteration, two iterations, three iterations.</p>
<p>So there's three iterations here.</p>
<p>So this is this three is log in iterations.</p>
<p>Because again, oh, all log in, just means Oh, log base two of eight.</p>
<p>Because our n is a, n, log base two of eight is three, because two to the power of three equals eight, which is our n.</p>
<p>And that is why this non recursive function is oh log in, because there will be log in iterations 123.</p>
<p>Before this while loop ends.</p>
<p>I hope that makes sense.</p>
<p>To start, we should understand that in order for binary search to work, the array that you were searching must be an ordered array, both ascending and descending order two arrays will work.</p>
<p>Let's start by visualizing our array.</p>
<p>In practice, this is much more useful as the size of an array becomes much larger, but we will stick with an array containing nine elements to help us understand the concept more clearly.</p>
<p>So let's assume that we want to check our array to see if the value 100 exists inside of the array.</p>
<p>The naive solution would be to iterate through each element of the array checking to see if the value is equal to 100, like so.</p>
<p>But for this method, we have to iterate through every element in the array up until the value that we're looking for.</p>
<p>What if we have to do this for an array containing 1000, or 100,000, or even a million elements.</p>
<p>This is where something like binary search can be useful.</p>
<p>So let's try this again.</p>
<p>So here, we're still wanting to check to see if the value 100 is in our array.</p>
<p>But this time, we'll use binary search to figure this out.</p>
<p>To start, we need to find the midpoint of our array, which is just the element in the middle of our array, our midpoint is here.</p>
<p>Now since our arrays in ascending order, we know that anything to the rate of our midpoint will be a value that is larger than our midpoint.</p>
<p>And everything to the left of our midpoint will be a value that is less than our midpoint.</p>
<p>So we need to figure out if this number 100, which we are searching for is greater than or less than our midpoint.</p>
<p>This will tell us which side of our array our numbers on.</p>
<p>So if we simply write out 43 is less than 100, we can actually see that the side of the array that our numbers on is this side.</p>
<p>To paint a full picture, let's for one second pretend that the number we are searching for is two and not 100.</p>
<p>In this case, two would be less than our midpoint 43.</p>
<p>Therefore, it will be on the left side.</p>
<p>And this Ladies and gentlemen, is why binary search will only work on ordered arrays.</p>
<p>Because without the order, there would be no way to tell which side the number we're searching for is on by comparing it to the midpoint.</p>
<p>Now let's get back to the original number that we were using for our example.</p>
<p>So now that we know that 100 will be on the right side of our midpoint, we can completely do away with anything to the left of the midpoint including the midpoint.</p>
<p>So what we're left with is this, what we've done is we've essentially cut the array in half.</p>
<p>To put this in perspective, let's imagine that we have an array with 1 million elements and we divide it by two.</p>
<p>In just one step, we will have cut down the number of elements that we would need to search by 500,000 elements as opposed to iterating through all 1 million elements and searching that way.</p>
<p>And it doesn't stop here.</p>
<p>We will now do the exact same thing with this half of the array.</p>
<p>Let's remember that we are searching to see if the number 100 exists within our array, we will first need to find our midpoint.</p>
<p>Now don't be confused by the even number of elements in this array.</p>
<p>Although there won't be an even number of elements on each side of our midpoint, it does not actually matter because we actually just need to split the array approximately in half.</p>
<p>For example, to find the mid and code we will do something like divide the length of the array which is four by two.</p>
<p>That resulting to we would use as the index of our mid.</p>
<p>So let's write out the indexes of this array remembering that arrays are zero Meaning that the starting index will be zero.</p>
<p>And if we take this resulting two and see what value it points to, we see that our mid is 100, which is the number that we are searching for.</p>
<p>So in that case, we would be done, we have found our number.</p>
<p>But to prove that which one of these we choose to use doesn't actually matter, let's explore what would happen if the mid 54 were used is the number that we're looking for greater than or less than our mid 54.</p>
<p>Our number is greater than 54.</p>
<p>So that means that we can get rid of the left side.</p>
<p>And what we're left with is an array containing only two elements, which again, is an even array.</p>
<p>So we have no way of determining which one we should choose as our mid let's see, what would happen if we use 124 is our maid is 124 greater than or less than 100.</p>
<p>It is greater than, so we can ignore the right half of this array.</p>
<p>Now we're left with an array containing only one element.</p>
<p>So our so called midpoint can only be this element.</p>
<p>And this element is the number we're searching for.</p>
<p>So we're done here.</p>
<p>So as you can see, regardless of if you have an auto array or an even array, as long as it is ordered, the search for element will be found if it exists in the array.</p>
<p>And that Ladies and gentlemen, is how binary search actually works and why it is useful.</p>
<p>So let's just start by creating a file and we can call it log in dot j s.</p>
<p>For binary search to work, the array that we're searching must be in either ascending or descending order.</p>
<p>So you can't just have a randomly ordered array and use binary search on it.</p>
<p>So that's just something to keep in mind throughout the rest of this tutorial, our binary search function is going to take in four arguments, it's going to take in an array, and the array is just going to contain integer values, which will need to be ordered, so we will just do one through eight, we'll also need to pass in the first index of our array to the function, we'll just call it start.</p>
<p>And that's just going to be zero.</p>
<p>And we will need to take in the last index of our array, which we'll just call end.</p>
<p>And we can get that by getting the length of the array and subtracting one from it.</p>
<p>The reason we need to subtract one from the length of the array is because the index is of the array are actually zero based, but the array itself, the length is actually not zero based, it's just going to be the number of elements in the array.</p>
<p>So the array length is going to be eight, but the last index of the array of length eight will be seven.</p>
<p>So that's why we subtract the one.</p>
<p>And then last but not least, we'll need to take in a target value, which is the value that we're searching for.</p>
<p>And we're going to just search for eight.</p>
<p>And then we can start building our function.</p>
<p>And we'll just call it binary search.</p>
<p>And it'll take in the array start at the end and the target.</p>
<p>And this function is actually going to be a recursive function.</p>
<p>So to start this function off, we need to find the middle index of our array.</p>
<p>So you'll notice that we're using a built in function math dot floor here.</p>
<p>And the reason we're using this is because if we go to the definition, it says that it returns the greatest integer less than or equal to its numeric argument, which basically just means that if this the division expression within our parentheses, within our function, parentheses return something like 5.5, the value assigned to MIT would only be five, because we don't want to take into consideration anything after the decimal point because we just want to find an index, which of course there wouldn't be an index 5.5.</p>
<p>So therefore, our mid would just be five.</p>
<p>And the next thing we would want to do is check to see if our midpoint is actually the number that we're searching for, which is our target.</p>
<p>So this would basically return true if the mid value of our array is actually the target that we're looking for.</p>
<p>So we're returning true because that means that the value that we're searching for exists within the array, and we would be done here.</p>
<p>And actually, I just realized I might actually be confusing you guys by referring to our mid and our mid value interchangeably.</p>
<p>So this mid here is actually the index of our mid which we're trying to get we're trying to get the index.</p>
<p>so here we can just add index as well.</p>
<p>So when I say our mid, I'm actually referring to the valley So we actually want to return true if the value that's at our mid index is equal to our target value.</p>
<p>So if the value at our mid index is not equal to our target, we then want to go on to check to see if the value at our mid index is greater than or less than our actual target.</p>
<p>So actually, this should be made index.</p>
<p>Sorry about that.</p>
<p>And actually, we have another error here.</p>
<p>So we'll start and then target should be here.</p>
<p>That should work.</p>
<p>So yeah, let's take the time to understand what's happening in this line of code here.</p>
<p>So if the value at mid is greater than our target, then that's going to mean that our target is actually in the left side of the array.</p>
<p>Because if we look here, and we take into consideration that five is going to be our mid in this situation, in the first execution of this function, five is going to be our mid.</p>
<p>And if five is greater than the number that we're searching for, then that means number that we're searching for is going to be in the left side, because if five were, if the number that we're searching for were greater than five, then it would be in the right side of the array, because our array is in ascending order.</p>
<p>So this is to check to see if the item that we're searching for is in the left side of the array.</p>
<p>And if it is, what we're going to do is, we're going to pass in our start, which is going to remain the same.</p>
<p>So we're going to keep the same start, which is going to be in this case, it's going to be index zero, and then our end is going to be mid minus one minus one, because we're going to actually do away with our actual current mid and actually, well, this should be nid index as well.</p>
<p>We only need to assign the current mid minus one to our in variable, because our next execution of the function would have this as our end, and then this as our start, and therefore we'd only be searching 1234, which we would then in turn find the mid for 1234.</p>
<p>And then we would do the same thing.</p>
<p>So now what would happen if the target value that we're searching for is greater than our mid value? Well, let's see.</p>
<p>So in this particular case, the target value would be less than or mid.</p>
<p>So that would mean that the target value would be in on the left side of our right.</p>
<p>But if that were not the case, then if our target is larger than our midpoint, then we would do something like else return.</p>
<p>So we're still going to function still going to call itself of course, but this time, we're going to pass in the array, the array, and instead of passing in the original start point, we're going to be passing in the midpoint index plus one.</p>
<p>And that's going to be our new start point.</p>
<p>And this is because we're starting from the midpoint to the right side of the array, because the actual value that we're looking for is in the right side of the array, and then at this point, our end can just stay the same, because the end is just the end of the array.</p>
<p>So let's let's have a look at this again.</p>
<p>So so let's again, pretend that for this execution, our mid is five and but this time, the actual value that we're searching for is greater than our midpoint.</p>
<p>So that means that it can't be on this left side, because everything on the left of our midpoint is going to be less than because our arrays in ascending order.</p>
<p>So it's going to be on this right side.</p>
<p>And if it's greater than our midpoint, then of course, we don't need to take five into consideration, which is why instead of doing mid index, and in instead of returning mid index and end to the function, we only need to return mid index plus one, which is going to be this index here, it's going to be index, it's going to be this value six at the index one index above our actual mid.</p>
<p>Now at this point, we're only searching our end and our mid plus one.</p>
<p>And we're only searching these three elements in the array.</p>
<p>That's what both of these conditions cover.</p>
<p>So this first condition covers if the item is to the left of our method, which is over here.</p>
<p>And the second one covers if the item that we're searching for is in the right environment.</p>
<p>And this is how binary search works.</p>
<p>This is why binary search is more efficient than say linear search.</p>
<p>Because we don't need to check every element in the array we can actually essentially eliminate half of the array by knowing whether or not the item that we're searching for is less than or greater than The midpoint.</p>
<p>So let's go ahead and see if we can actually run this function and get it to work.</p>
<p>And I'm going to tell you right now, we're going to try and run it twice, we're going to try and run it searching for the actual value that we know that's in the array.</p>
<p>And we're going to try and run it searching for a value that's not in the array.</p>
<p>And you'll see that we're missing something in this function.</p>
<p>So let's go ahead and try and run it.</p>
<p>Now to run it.</p>
<p>Obviously, we have to invoke the function.</p>
<p>So we'll go binary search.</p>
<p>And we're going to pass in array, start and end target.</p>
<p>And we're going to save that.</p>
<p>So we will try and write it by just using node, login dot j s.</p>
<p>And we broke it.</p>
<p>Nice.</p>
<p>Got to add in the target here.</p>
<p>So it caused the entire function to fail.</p>
<p>See it again.</p>
<p>Okay, so let's see what happens if we actually return the value, I mean, console log the return value of the function.</p>
<p>And we get true because eight is found within the array.</p>
<p>But what you'll see here is if we search for something that doesn't exist within the array, we're going to break it again.</p>
<p>So 10 does not exist.</p>
<p>So let's try it again.</p>
<p>And we got maximum call stack size exceeded, because let me show you what it means maximum call stack size exceeded.</p>
<p>So what we're doing is, every time we don't meet this condition true, we're going to call binary search again, which is we're calling the functions recursively calling itself again.</p>
<p>And if we're, if we're searching for a number that doesn't exist within the array, binary search is basically going to keep calling itself recursively.</p>
<p>And there's never going to be a point at which it stops.</p>
<p>Even if it doesn't find the item within the array, it's still going to continue to recursively call itself until eventually we reach the maximum call stack size, which is basically you've exceeded the amount of memory allocated to this particular application.</p>
<p>So to solve this issue, what we want to do is we want to add a base condition that will stop the function from recursively, calling itself after it's checked the entirety of the array.</p>
<p>So we can do if start is greater than and then return false.</p>
<p>So the reason why this works is because if the targets not in our array, it either means that the target is larger than the largest value in our array, or it's smaller than the smallest value in our array.</p>
<p>So that means our function will keep checking our array until eventually we get to either the largest item if the target is larger than the largest value in the array, or it gets to the smallest item if the target is smaller than the smallest item in the array.</p>
<p>And at that point, the start and the end values will be equal and at the point where both the start and the end values are equal passing our start in our into either this line, or this line, well, in effect, make the start greater than the end.</p>
<p>And now we can run this again using this tin that doesn't exist within our array.</p>
<p>And as you can see, we get false.</p>
<p>And if we even added negative here, negative 10 doesn't exist.</p>
<p>So we get false as well.</p>
<p>And let's see, what else can we try.</p>
<p>just tried to we know two exists.</p>
<p>And then and we get through.</p>
<p>So let's change this back to a to get a feel for why this function is oh log in, let's actually let's go ahead and create a longer array here.</p>
<p>So currently, our array only contains these eight elements.</p>
<p>And it's going to be kind of hard to get a general understanding of the way that our input scales with such a small array, so we can go ahead and just just empty out our array, and we'll just create our own array.</p>
<p>So let's see, the trade our own array we can just do for i equal zero, i is less than 1024 i plus plus.</p>
<p>And then here for each iteration of AI, we can just do grade up, push high.</p>
<p>And let's think let's actually make eye one.</p>
<p>And then we'll make this less than or equal to.</p>
<p>And then after that we can console.</p>
<p>log our array.</p>
<p>And for now, let's just comment this out.</p>
<p>And let's see.</p>
<p>Okay, so at this point, we have a longer array, which will hopefully help for you guys to visualize how the input is scaling when I do some console log trickery here.</p>
<p>So yeah, so we don't need to log this anymore.</p>
<p>Just delete that, actually.</p>
<p>So we're creating a new array here.</p>
<p>And it's going to be an array that has elements from one to 1024.</p>
<p>And for the purposes of this example, I don't want us to find the element, I mean, the item in the array, so we're just going to change this to something that doesn't exist within the array.</p>
<p>So we'll just put it like that 100,000 does not exist within our array, and also the end, and this is getting the end from this current array.</p>
<p>So we're going to need to bring this down to after we create our full array.</p>
<p>So this array is empty here, and then we're adding all of the values in this for loop and then we get the end of the array.</p>
<p>And the start, of course, can still be zero because it's zero.</p>
<p>And also, we can go down here and delete till the, we don't need to console log this anymore, because we're going to do another console log.</p>
<p>So we're going to execute the function here.</p>
<p>And then here is where we're going to try and make some magic happen.</p>
<p>So for each call to binary search, like each recursive call, we want to not just recursive call the first call for the first call.</p>
<p>And each recursive call, we want to log with the array that we're searching through is looking like.</p>
<p>So in the beginning, it's the full array, which we just showed when we console logged in earlier.</p>
<p>And then at each call to the function, the rate is going to essentially be halved.</p>
<p>So it's going to look something like let's see, console dot log will do array dot slice.</p>
<p>And we're going to do the start and the end.</p>
<p>So what that does is, it's only going to show the parts of the race from the start to the end, it's not going to show the full array anymore.</p>
<p>And let's see if that works.</p>
<p>so here we can do node, log in. </p>
<p>Okay, and yeah, that worked.</p>
<p>So maybe I can make this smaller.</p>
<p>So you can see, so when I make it smaller like this, it's kind of easy for you to see like, well, at this point there is too long to show its entirety.</p>
<p>But you can still see what's happening here.</p>
<p>So like since the value is greater than the left side of the array, you can see that all these lower values are going to essentially be eliminated, and it continues to get halved and have an halved.</p>
<p>And here's where you can start to see visually what's happening here like I can see that it's the ray is continuing to get smaller and smaller.</p>
<p>To understand ovan login, we will take this small function into consideration.</p>
<p>This function has a complexity of ovan login.</p>
<p>Let's step through this code line by line so that we may understand what is happening here.</p>
<p>This function takes one argument in which for the sake of this example will be for we then declare another variable y which we will set equal to n, we will get to what this variable Y is for later.</p>
<p>And at this point, we have a while loop that iterates through n until n is equal to one.</p>
<p>For every iteration through in this code within the while loop is run.</p>
<p>Let's visualize this.</p>
<p>For the first iteration of the while loop in starts off is four but we divide it by two, so n is now equal to two.</p>
<p>Then we get to this line of code which is the start of a for loop.</p>
<p>This is where this variable y comes in.</p>
<p>The reason we declared this variable before the start of the loop is because n is getting divided by two for each iteration.</p>
<p>This in turn is reducing the size of the variable n.</p>
<p>But for this inner for loop we needed to iterate through the original size of our original n.</p>
<p>So we stored the original end in a separate variable.</p>
<p>Okay, back to this inner for loop.</p>
<p>For each iteration of this for loop up until the size of n we will log or print the value for I.</p>
<p>Once this is finished, we move on to the next iteration of the while loop and repeat the process going into this iteration In is now two, we start by dividing in by two.</p>
<p>So n is now one.</p>
<p>And once again, we iterate through our inner for loop up until the size of y.</p>
<p>Now at this point, you will notice that our n is now one.</p>
<p>If we check the condition of our while loop, we see that we only want to iterate while n is greater than one.</p>
<p>So the while loop will now terminate, and the function is finished.</p>
<p>Now, after all is said and done, and with everything written out, we can see that there's a top level loop here.</p>
<p>And there's an inner loop for each iteration of the top level loop.</p>
<p>So this is where the magic happens.</p>
<p>So pay close attention.</p>
<p>For every iteration through the top level loop, which iterates until n is one in is divided by two.</p>
<p>This means that this top level loop never actually iterates through the full size of our input in the value for n is being split in half for each iteration, which is why we would say that this top level loop has a complexity of Oh login.</p>
<p>If you are confused about why this top level loop is Oh, log in, let's take some time to prove it by writing it out.</p>
<p>So this is Oh, log in.</p>
<p>Let's plug in some numbers.</p>
<p>Now if you've watched my video on Oh, log in, you know that in computer science, the base of a logarithm is always two unless stated otherwise.</p>
<p>So this can be rewritten as log base two of four, four, because we're replacing in with our actual input for n, which is four.</p>
<p>And log base two of four is two, because you need to raise two to the power of two to get four.</p>
<p>And as you can see, this makes sense, because for this top level loop, we only iterate two times.</p>
<p>So for this top level loop, we have log base two of four equals two.</p>
<p>Now we need to take a look at what is happening in each of the two iterations of the top level loop.</p>
<p>For each iteration, we loop through the full size of y, which is the original size of n.</p>
<p>So that means that each of these inner loops has a complexity of O of n, which just means that processing time increases linearly with the size of n.</p>
<p>Now this is where we bring everything together.</p>
<p>O of n log in really just means O of n times log in.</p>
<p>And if we plug in some numbers here, we get this.</p>
<p>Because remember, log base two of four equals two.</p>
<p>And if you look at our visualization, it makes perfect sense, because for each iteration of the top loop, we iterate through the entirety of y, which is our original value for n.</p>
<p>And that, ladies and gentlemen, is how you visualize all of in log in.</p>
<p>So we can start by creating a file, let's just call it merge sort, well then create a function, which will also call merge sort.</p>
<p>The argument to this function is going to be the array that we're looking to sort for the first portion of this function, we'll need to make sure that the array that we're passing in has a length greater than one, we will need to do this because if the array is only if length one and there's only one element in the array, then it is already sorted.</p>
<p>This will also be our base case, as this merge sort function is going to be a recursive function.</p>
<p>Next, we're going to need to split our array in half.</p>
<p>To do this, we'll first need to find the middle index of our array.</p>
<p>So with this math dot floor here does is it makes sure that we only take into consideration the base number from the result of the division.</p>
<p>So for example, if we divide a number that results in I don't know, let's say 5.5, we wouldn't take into consideration the number after the decimal point.</p>
<p>So we would only return to the variable the number five.</p>
<p>And this is because when taking indexes into consideration, there is no index 5.5 or 2.2 or 1.1, there would only be an index one, five or two.</p>
<p>So this is why we're using math dot floor here.</p>
<p>And once we have the middle index of our inputted array, we can then split the array in half and create a separate array for the left side and a separate array for the right side.</p>
<p>So we can do that by just creating a new array left array, and then we can set left array equal to the input array dot slice, and then the indexes are going to be the arguments that we passed.</p>
<p>So basically slices from and to.</p>
<p>So we want to slice from the first index of our input array.</p>
<p>And we want to slice up until the middle index that we just that we just got.</p>
<p>And that's because we want the left side of the array.</p>
<p>So let's say for example, the array looked like this.</p>
<p>And we win, we went ahead and got our middle index, which would be something like this three here.</p>
<p>And then we want to create an array starting from this zero index, up until our middle index, which would be the left half of the array, and then we would go ahead and do the same thing for the right half of the array.</p>
<p>So we're going to actually go ahead and do the same thing for the right half of the array, we'll just call it right array.</p>
<p>And we'll do array dot slice again.</p>
<p>But this time, we're just going to do from the middle index all the way until the index, the last index of the array, and the way we get the last index of the array is just by using array dot length.</p>
<p>And this here can actually be a bit confusing, because we know that array dot length gives us the length of the array, which is the number of elements in the array.</p>
<p>And we also know that array index is zero based.</p>
<p>So basically, if the length of an array is five, there will only be index 0123, and four, there won't be an index five.</p>
<p>So here, you might be wondering how this is actually working.</p>
<p>And it's because actually, there's an error.</p>
<p>And this method method is not slicers, just slice, but it's because this slice method slices up to in but not including in.</p>
<p>So basically this end value, it's not included in the actual array, slice, only the value before will be included.</p>
<p>So for example, if we have an array that looks like that looks like this, the end that we would use for this array would be three, even though that there's only index 01.</p>
<p>And two, it will slice up until end not including in so this is why we don't need to subtract a one from this because if we were to subtract one from array dot length, and use that as the last index, or the end index that is passed to the slice method, then we actually wouldn't get the full array, we would only get up until this one, but not including this one.</p>
<p>So they were able to only look like this in this slice.</p>
<p>I hope that makes sense.</p>
<p>It's a bit confusing.</p>
<p>And also keep in mind that with this example I just gave above, I'm actually not taking the middle index into consideration.</p>
<p>So for this array, in particular, it would look something like array dot slice, well, slice and there will be zero index up until array dot length minus one.</p>
<p>Anyways, last but not least, for our actual emerge cert function, what we're going to need to do is implement the recursive portion of the function, which is we're going to return and bear with me, we're going to return a helper function that we haven't created yet.</p>
<p>And we're going to call it merge.</p>
<p>And within this marriage helper function, we're going to accept two parameters, which is going to be the left array and the right array.</p>
<p>And what we're going to pass to merge is going to be the recursive call to merge sort.</p>
<p>And then our left array, and we're also going to pass in the recursive call to merge sort, and our right array.</p>
<p>This is going to seem a bit confusing right now.</p>
<p>But just bear with me, I will explain how this is working.</p>
<p>And I will try to make things clear for you.</p>
<p>But for now, we don't actually have this merge function, so we need to go ahead and create it.</p>
<p>So let's go down here and create this new helper function called marriage, which will take in the left array, and the right array.</p>
<p>Oh, sorry, and it's not merger, it's merge.</p>
<p>Now this function is going to be the function that actually merges the two arrays.</p>
<p>So the way that Merge Sort works is we use a divide and conquer approach in which the input array is basically halved until we have in arrays of length one, and at that point, arrays of length one, as mentioned above, when we created this space case, here, an array of length one is already sorted.</p>
<p>So to visualize this, if we have an array, that is one, there's only one element in this array, so obviously, this one is going to be the first and last element in the array.</p>
<p>So there's no need to sort it because there's nothing to compare it to with.</p>
<p>What we do in this actual merge function is we're going to bring these sorted arrays together and compare them and then sort those individual one element arrays.</p>
<p>So one thing to keep in mind throughout the process of writing this merge function is that this merge function is always going to take in two already ordered arrays, starting with the ordered arrays of length one.</p>
<p>So to start, we're going to To create a variable, which is going to just be the result array, and it's going to start off as an empty array.</p>
<p>So these all equals just an empty array.</p>
<p>And we're also going to define our base indexes for the left array and the right array, but could index equal zero.</p>
<p>Now we'll do the same thing for right.</p>
<p>Next, we'll create a while loop that's going to compare the two arrays element by element.</p>
<p>And actually stick here, this length.</p>
<p>So within this while loop, we're going to compare each element of both arrays and whichever element is less than the other will get added to the result array will then increment the index of whichever element got added to the result array because that element no longer needs to be compared.</p>
<p>If you're a bit confused by this, just bear with me, I'll actually create an illustration for you to understand it better.</p>
<p>But for now, let's just write out the code.</p>
<p>Let's imagine that the rays that we want merged look like this for the left array and this for the right array.</p>
<p>Now keep in mind that the merge helper function merges ordered arrays, so it will not work on unordered arrays.</p>
<p>In this example, we are merging two ordered arrays of length three to show the entirety of the functions functionality, but this will also work for naturally sorted arrays of length one.</p>
<p>So for this while loop to continue, both left index and right index need to be less than the length of their corresponding arrays.</p>
<p>As you can see, these indexes are incremented, every time that index is element is pushed to the result array.</p>
<p>So if we draw this out, it looks something like this.</p>
<p>Here are the two arrays and their indexes.</p>
<p>In this next line, we check to see if the element at the left array index, which is currently zero is less than the element at the right right index, which is also zero.</p>
<p>So it's three less than one.</p>
<p>No.</p>
<p>So that means we do what's in our else condition, which is push the right array element at its current index to the result array and increment the right array index.</p>
<p>And now our right array index is one so we can move this.</p>
<p>And then once again, we do our comparison at the top of the for loop is three less than six? Yes.</p>
<p>So we push three onto our result array and increment our left array index.</p>
<p>And we can move this over as well.</p>
<p>And back to the top of our for loop again, is 12 less than six? No.</p>
<p>So we're going to use the code in our else condition, which is push the right array element six to the result array and increment the right index.</p>
<p>And again, we will move this and now is 12 less than 15.</p>
<p>Yes, so we push the 12 from the left array to the result array and then increment the left index as well as move this arrow to the new left index.</p>
<p>Now is 16 less than 15? No.</p>
<p>So we move on to our else condition and push the 15 from the right array to our result array and increment the right index by one.</p>
<p>Now at this point, this while loop will terminate because if you remember this while loop will only continue if the left index is less than the left arrays length, and the right index is less than the right arrays length.</p>
<p>At this point, our right index is equal to the right arrays length.</p>
<p>As you've probably already noticed, there's still a 16 left in the left array that has not yet been pushed to the result array.</p>
<p>But the while loop is already complete.</p>
<p>So what do we do? After the while loop, we're going to add another line of code that looks like this.</p>
<p>So this return is going to return a single array that is a combination or concatenation of three arrays the result array, a slice of the left array and a slice of the right array.</p>
<p>So this slice function if we only pass one index to it, it will be used as the start of the slice and will slice up until the end of the array.</p>
<p>Let's break this down.</p>
<p>So if you remember from the last slide, our result array currently looks like this.</p>
<p>And we're going to add to it a slice from the left array starting from the left index that we incremented which is two which results in array containing only this part And we're going to add to that a slice of the right array starting from the right index that we incremented, which is three, which results in an empty array, because index three would be here.</p>
<p>And with all of those combined, the result being returned is an ordered array that looks like this.</p>
<p>Now let's go ahead and add in the return that we just discussed in the illustration.</p>
<p>And this completes our merge function.</p>
<p>And now that the merge function is complete, our merge sort function is also complete.</p>
<p>And now we can go ahead and test this out.</p>
<p>To test this, we will need to create an array.</p>
<p>And this array, we will need to pass to our merge sort function.</p>
<p>And there you have it, our sorted array.</p>
<p>And if we take the time to go back in here and have a look at the code, you'll see that within this merge sort function, we're dividing the input array into recursively, which makes this Merge Sort portion of the function of log in.</p>
<p>And actually, this merge function is all event because it needs to touch every element within both arrays to actually sort them.</p>
<p>So with this merge function being old in and the actual recursive merge sort function being of login.</p>
<p>For every level up until the depth of this recursive function, we're actually going to be doing this merge, which is O of n.</p>
<p>So to get the overall time complexity, we would just have to multiply the depth of this recursive function by O of n, which is O of n log in because of N log N is just multiplying in by log in and log in, in this case will be the depth at which this recursive function needs to traverse.</p>
<p>So let's visualize this merge sort function, we'll go through the code line by line.</p>
<p>So let's imagine that we have an array that looks like this.</p>
<p>So this array here will be our input array.</p>
<p>So this is the array that we're going to pass to our merge sort function here.</p>
<p>So this array is this array.</p>
<p>So the first portion of the code here, it just checks to see if our array is greater than length one, because an array of length one is already sorted.</p>
<p>So if we get an array of length one here, we're just going to return that array as an already sorted array.</p>
<p>But if the array is greater than length one, then we're going to move on to this portion of the code.</p>
<p>And this portion of the code is where the divide and conquer approach is implemented.</p>
<p>So basically, here, we're going to split our input array.</p>
<p>By getting the middle index of the array, we're going to split it into two separate arrays, which will look something like this.</p>
<p>And these individual arrays left array and right array.</p>
<p>After their split, they're going to be once again passed to the merge sort function.</p>
<p>Once again, we end up at this portion of the code, because this array and this array are individually being passed to this merge sort function.</p>
<p>So for each of these, we end up at this portion of the code.</p>
<p>And both of these arrays are not less than length do, we have an array of length two, and we have an array of length three.</p>
<p>So they're going to move on to this portion of the code in which we use the divide and conquer approach once again.</p>
<p>And let's go ahead and write that in here actually, because it's important.</p>
<p>So once again, we're going to get the middle index of our array and create a left right and a right array by splitting the single array on its middle index.</p>
<p>So you will notice that this array, this array and this array are already of length one.</p>
<p>And as, as we've seen here, we're going to pass these arrays of length one to merge sort, we're going to pass these arrays in to merge sort, and then they're going to get to this conditional, and they're less than length two.</p>
<p>So we're just going to return these rays.</p>
<p>So for these arrays, we can stop.</p>
<p>But this right here is of length two.</p>
<p>So this array is going to get to this portion of the code again, and we're going to split it.</p>
<p>And now, these arrays are of length one, so we can stop here as well.</p>
<p>So these calls to merge sort, are the same as these calls to merge sort.</p>
<p>But we still haven't called merge.</p>
<p>And what merge is going to do is it's going to take two already sorted arrays, and it's going to merge them together into one single sorted array.</p>
<p>And what that's going to look like is, it's going to be called here, these results are going to be merged together into one sorted array.</p>
<p>So these two sorted arrays are going to be combined and returned here as a sorted array of length two.</p>
<p>And the same thing will be done here.</p>
<p>We're going to merge.</p>
<p>And these two sorted arrays are going to be combined and returned here as a single sorted array.</p>
<p>And we'll do the same here.</p>
<p>Merge.</p>
<p>And these two sorted arrays are going to be combined and returned here as a single sorted array.</p>
<p>And last but not least, we're going to merge here.</p>
<p>And these two sorted arrays are going to be combined and returned here as a single sorted array.</p>
<p>And let's not forget our initial call to merge sort.</p>
<p>So that's how we can visualize recursive merge sort.</p>
<p>But you're probably still wondering what this merge function is actually doing.</p>
<p>So as mentioned before, this merge function takes two already sorted arrays, and it combines them together into one sorted array.</p>
<p>And that function looks something like this.</p>
<p>So as you can see, merge takes in a left array and right array, both of which are sorted, and then it will return this result array, which is a combination of both the left array and the right array sorted.</p>
<p>So to understand the time complexity of merge store, we'll take a array and array of length four into consideration.</p>
<p>So this input array will pass to the merge sort function.</p>
<p>And what that call to merge sort will do is divide the array approximately in half and those halves will be passed to merge sort recursively at this point, We have our arrays of length one.</p>
<p>So we can't split these rays any further.</p>
<p>And to understand the time complexity of merge sort, we need to understand, oh, log in.</p>
<p>So as we know, in computer science, so log in is the same as log, base two in, and in this case, in is the length of our array here, which is four.</p>
<p>So in the reason you need to understand login, is because this divide and conquer approach that we're implementing here is login.</p>
<p>That is to say that log base two, a four, which is our in our n is four equals two.</p>
<p>And that's because two to the power of two equals four, which means that for an array of length four, there will be two levels to our recursive tree structure.</p>
<p>And we can see that here, you have level one, and we have level two.</p>
<p>So this is a level, and this is a level.</p>
<p>And for each one of these levels, what we need to do is we need to touch every element of n, because we need to sort them.</p>
<p>And in order to sort them, if you remember from our illustration of merge, within this merge function, the while loop within this merge function, needs to touch each element to compare the elements and create the merged array.</p>
<p>So that means that for each level, we need to merge.</p>
<p>And this merge function needs to touch every element of n.</p>
<p>So that means that each level is actually Oh, then.</p>
<p>And there are log n levels.</p>
<p>So O of n times log in really just means Oh, of four, because four is our in, in is for 1234.</p>
<p>So four is our n times log, base two, a four.</p>
<p>And as we seen here, log base two, a four is actually just two times four.</p>
<p>So the number of elements in the array, and the number of levels that we need to traverse, so for every level, we need to touch in elements in the array, which is two times four.</p>
<p>And that's why mergesort has a complexity of O of n log in.</p>
<p>So we'll start by examining this recursive implementation of Fibonacci.</p>
<p>So let's imagine that we pass the number four to our fib function.</p>
<p>So at this point four is our value for n.</p>
<p>So after we call this function, we'll end up at our first if block.</p>
<p>And this if block just returns zero if n equals zero, and then we move on to a second if block in the second if block just returns one if n equals one.</p>
<p>So once we pass the number four into our function, we'll end up at this first if block.</p>
<p>And both of these if blocks are base cases, because as we know, with recursive functions, we need to have a base case so that the function doesn't continue to call itself even after we're finished.</p>
<p>So we pass the four into our fib function, and four is not equal to zero, so we don't return zero, and four is not equal to one, so we don't return one.</p>
<p>So we end up here.</p>
<p>And what this return does is it adds together the result of two more calls to the Fibonacci function, this first Fibonacci function, we're going to call with our n minus one, so four minus one, and the second one, we're going to call with our n minus two, so four minus two.</p>
<p>So let's have a look at what that looks like.</p>
<p>And as we can see, four minus one is just equal to three, so this would actually just be three.</p>
<p>And same for here.</p>
<p>This would just be equal to two.</p>
<p>So at this point, we have to call to our favorite our Fibonacci function.</p>
<p>One which we're passing three is our in and one numerator passing two is our in.</p>
<p>So for both of these calls, neither one of these will Return at these IP blocks.</p>
<p>So we'll end up back down here again, which will look like this.</p>
<p>And again, we can just do the math in the parentheses.</p>
<p>And let me just make this a little bit smaller.</p>
<p>So at this point for these three calls to the Fibonacci function, we're going to reach our base case, because we're passing zero for this call.</p>
<p>And we're going to just return zero at that point.</p>
<p>And we're passing one for these two calls.</p>
<p>And we're going to just return one at those points.</p>
<p>So these are going to be complete.</p>
<p>These ones are done.</p>
<p>And for this function we're passing to is our n, which isn't equal to zero and is equal to one.</p>
<p>So at this point, we'll then again go down to this portion of the code.</p>
<p>And now these two have reached our base cases as well.</p>
<p>And one more time, I'll need to shrink this.</p>
<p>So now we'll get into the reason why this recursive Fibonacci function is an exponential function.</p>
<p>First, let's start by observing this recursive tree structure.</p>
<p>So as we can see, here, we have one, two and three levels to this recursive tree structure.</p>
<p>So we can write that out level one, level two, and level three.</p>
<p>And for this first level, here, we're calling the fib function two times.</p>
<p>So one, two, and for this second level, we call our fib function four times 1234.</p>
<p>So this level, we make two calls to the Fibonacci function in this level, we make four calls to the Fibonacci function.</p>
<p>And let's just ignore this third level for now.</p>
<p>And let's just focus on these top two levels.</p>
<p>So two here is the same as two to the power of one and four, here's the same as two to the power of two.</p>
<p>And as you can see, our exponents correlate with our levels.</p>
<p>So actually, if these three functions were to make their two additional calls to recursive Fibonacci, we would have something that looks like this.</p>
<p>So we have two calls here, two calls here, and two calls here.</p>
<p>So we'll just write this out.</p>
<p>So let's imagine that these are also additional costs to the recursive Fibonacci function.</p>
<p>And let's just imagine that that's the case for one second, just so that we can get a better understanding of why this function is of exponential time complexity.</p>
<p>So now, if we count the calls at this third level to our recursive Fibonacci function, and keep in mind that these calls won't actually be made, only these ones will be made.</p>
<p>But we're just writing this out so that we can get a better visualization of what's happening here.</p>
<p>So if we counted out these calls to the Fibonacci function, it would be 12345678.</p>
<p>So we would have eight calls at this third level, and eight is the same as two cube or two to the power of three.</p>
<p>And as you see, once again, our exponent corresponds with our level.</p>
<p>So that means that if our n is four, we would go three levels deep.</p>
<p>And at each level, the number of calls to our Fibonacci function increases exponentially.</p>
<p>But you might be wondering, since our n is four, and we stop here, when it's two to the power of three, as opposed to two to the power of four, how does that result in this function being off to to the end? Well, it's actually quite simple.</p>
<p>So in actuality, this Fibonacci function is O of two to the n minus one.</p>
<p>And if we write this out, you can see it's o of two to the fourth power minus one, which is just equal to O of two to the third power, which is the same as the number of calls made at this third level, right.</p>
<p>And if you remember, in bego, we ignore constants.</p>
<p>So if in actuality, this function is O of two to the nth power minus one, and we ignore the constants, that means that we're going to ignore this minus one, which results in the time complexity of this function being o of two to the N.</p>
<p>And at this point, you're probably wondering how we're able to add these function calls in here.</p>
<p>And in actuality, I only added these function calls in here to give you guys a better visualization of what's happening at each level and why we're considering this function to be off to to the internet.</p>
<p>Because it's easier to visualize if we actually write out these functions that we're actually not calling.</p>
<p>And we can do that because with bego, as we've learned, we're only looking for an upper bound, like we're not looking for a tight bound, we're not looking to be very specific, we're only looking for you can say an estimation of the worst case scenario.</p>
<p>So as you can see here, on this left function, we're calling fib and then we're subtracting one from n.</p>
<p>And on this right one, we're calling fib and then we're subtracting two from n.</p>
<p>So this right side of the tree is always going to be shorter than this left side of the tree, there's always going to be this empty space.</p>
<p>Because on this right side of the tree, at every level, we're subtracting two.</p>
<p>And at this upside of the tree, at every level, we're subtracting one.</p>
<p>But when we're taking bego into consideration, we don't need to worry about this.</p>
<p>And regardless of what number we pass into this function, at the bottom most level, there's always going to be a gap on this right side.</p>
<p>But that's okay, because we're only looking for an upper bound.</p>
<p>So these are just here to help you visualize what is actually happening and why this function is considered to be of exponential growth.</p>
<p>And that is why recursive Fibonacci is of exponential time complexity, I hope that makes sense.</p>
<p>We'll start with the function that will call F.</p>
<p>And this function will be a recursive function.</p>
<p>So this first portion of the code is going to be our base case.</p>
<p>So if the value for n pass to this function is equal to zero, then we're going to print these stars.</p>
<p>And then we're just going to return but if we pass a value to n, that's not equal to zero, then we will go down here to this for loop.</p>
<p>So let's start with an example.</p>
<p>So let's say that we pass the number three to our function, what will happen first is we'll check to see if in which is three in our case is equal to zero, which it's not.</p>
<p>And then we'll move on to this for loop.</p>
<p>And what this for loop is going to do is, for every iteration of in, for every iteration, up until three from zero up until three, which is our end, we're going to recursively call this function again, this time using our n minus one.</p>
<p>So let's, let's try to visualize this.</p>
<p>So if we pass through to this function, and we end up at this for loop, we can write it out like this.</p>
<p>So for each index, up until three, but not including 3012.</p>
<p>And the reason we're only doing for each index 012 is because here, I starts off at zero, and we're going to iterate through our input value in up until AI is no longer less than in.</p>
<p>So once I becomes equal to n, then we'll stop.</p>
<p>So if I were to be three, then we wouldn't go through this loop again.</p>
<p>So that's why it's 012.</p>
<p>And for each of these iterations, 012, we're going to call this function again.</p>
<p>And that's going to look something like this.</p>
<p>So if you look here, we're subtracting one from in that we're passing to the function at each iteration of this for loop.</p>
<p>So if n is three here, for each of these, n is going to be equal to two because we're going to subtract a one for each of these.</p>
<p>So these are actually going to be f two.</p>
<p>And for each of these, we're going to do the same thing that we did for the first call to this function to this f function.</p>
<p>But this time, we'll only iterate through indexes, zero, and one.</p>
<p>So each of these are their own individual calls to this recursive function, right.</p>
<p>And each of these needs to have their own for loop, which is this, this in this.</p>
<p>So at this point, f is two.</p>
<p>So we're iterating up until I is no longer less than two.</p>
<p>So we'll have index zero and one that we're iterating through and for index zero and one, we're going to do this and the same for this call to the recursive function for index zero and one or We're going to do this and the same for this one.</p>
<p>And I apologize if the writing here is getting too small.</p>
<p>But you'll see that this recursive tree gets very large very quickly.</p>
<p>So I'll actually need to shrink this down a little bit.</p>
<p>So that we can have more room.</p>
<p>So for each of these, we're going to call the recursive function again.</p>
<p>But this time, the function is being called with two minus one, which is going to mean that our in is going to be one.</p>
<p>Let's make this a little bit smaller, actually.</p>
<p>And again, I apologize.</p>
<p>So at this point, our for each is only going to happen once for index zero.</p>
<p>Man, this is getting really tiny.</p>
<p>Okay, so at this point, our F is one for all of these calls to the recursive function, and AI starts off as zero.</p>
<p>And as long as i is less than n, which is one, then we'll do this code.</p>
<p>And it's only going to be less than in when it's zero, which is this one iteration.</p>
<p>So for each of these calls to the recursive function, we're only going to call this function once for this first iteration, which is zero.</p>
<p>Now at this point, it's going to be f one minus one.</p>
<p>And f one minus one is actually going to be equal to zero.</p>
<p>So it's going to be f zero.</p>
<p>So we're going to be passing zero as our in to the function.</p>
<p>And it's going to be a little bit difficult to see because it's small.</p>
<p>But if we remember up here, in the actual function, our base case is if n is equal to zero, then we're just going to console log, and then we're going to return.</p>
<p>So for each one of these calls to the recursive function, we're going to perform this code this console log code.</p>
<p>And then after we perform this console log code, we're going to return so it's going to be finished, this entire function will be finished, because all of these are going to return, they're going to log the code, and then they're going to return.</p>
<p>And once all of these return, this entire function is going to be complete, it's going to terminate.</p>
<p>So instead of writing out console log, we'll just write that each of these functions performs log n after the log, the function will return.</p>
<p>So it'll stop.</p>
<p>So for the last time, I'm going to need to make this a little bit smaller.</p>
<p>Alright, so what we're left with when this function is finished is we're left with this tree structure.</p>
<p>And this tree structure shows how many recursive calls that we had to make to get to our base case for each of these recursive calls.</p>
<p>And if you look here, I'll go ahead and circle these so that you can see them more clearly.</p>
<p>If you look here, for each of these recursive calls to the function, we had to perform this code, we had to perform this code here for each of these recursive calls to the function.</p>
<p>So at the final level, where our base case was, we had to perform this code.</p>
<p>And if you count these, you'll see that this is 123456.</p>
<p>So six times we needed to perform this code passing three into our function caused us to need to recall this final recursive call to our function six times and perform this code six times.</p>
<p>And that number six is our key to understanding factorial time complexity, because if you look here, we have oh three factorial.</p>
<p>And the reason why is because our n is three, right? So it's, we're just substituting so all three factorial right and three factors.</p>
<p>Mario is six actually, because to get the factorial of a number, you just multiply every number up until that number.</p>
<p>And if we multiply two times one, we get two.</p>
<p>And if we multiply that two times three, we get six.</p>
<p>And, and again, we needed to execute this console log this code, we need to execute 123456 times.</p>
<p>And if we dig a little deeper, we'll see the three factorial is a result of multiplying every number up until three, which is also the same as multiplying every number from three down until one, which we can see if we look at how it progresses through our tree structure.</p>
<p>Here, we can see the first three is passed.</p>
<p>So first three is passed.</p>
<p>And then three times two is passed three times.</p>
<p>So times three times two is best.</p>
<p>And the result of three times two is six, and six times six times one is past.</p>
<p>So six times 123456 times one is past.</p>
<p>So this for loop first passes to three times, so passes to three times 123, which is the same as 333, times times two, three times 123, passing two.</p>
<p>And when we've asked to to the function, we do two iterations, so three times two iterations.</p>
<p>So we're going to do this, we're going to iterate through this for loop of two iterations, three times three times two, and then three times two is going to be six, because we do two iterations for each of these three.</p>
<p>So this, these iterations plus these iterations, plus these iterations equals six iterations.</p>
<p>And for each of these six iterations, we're going to pass one to the function, so six iterations, so six times will pass one to the function.</p>
<p>So that's here, six times one, six times one, and that is factorial time complexity.</p>
<p>I hope that makes sense.</p>
<p>So to understand space complexity, we'll take this function into consideration.</p>
<p>And this is a recursive function, that basically just returns a call to itself with its input in minus one, it's going to do this until we reach a base case where n equals zero, and then it's going to just return and at that point, this function will be complete.</p>
<p>So let's go ahead and draw with the execution of this function would look like.</p>
<p>So let's say that we passed the number five to this countdown function.</p>
<p>So with this first call to countdown with argument five, we'll end up at this base case.</p>
<p>And we'll see that our n five is not equal to zero.</p>
<p>So we'll move on to this part of the code, which is just calling this function again with five minus one.</p>
<p>And of course, five minus one is going to be four.</p>
<p>And once again, we'll end up here, and we'll call the function again with four minus one.</p>
<p>And we'll continue to do this until we pass zero as our end to the function.</p>
<p>And I will need to make this a little bit smaller.</p>
<p>And finally, we get to the call where we're passing zero as our into the function.</p>
<p>At this point, if n is equal to zero, that function will just return.</p>
<p>So to understand space complexity, it's actually quite simple.</p>
<p>So since this is a recursive function, each one of these calls exists on the call stack simultaneously.</p>
<p>So that means that if we call our countdown function with five, it's going to then call itself with four and at this point, this initial call still exists on the call stack.</p>
<p>And the same for when we call three.</p>
<p>These two calls still exist on the call stack, and all the way down until we reach our base case.</p>
<p>Every single one of these calls still exists on the call stack.</p>
<p>And each one of these calls takes memory.</p>
<p>So each one of these calls existing on the stack, they take up memory.</p>
<p>And this is how we come to an understanding of space complexity using this recursive function as an example, because if we're returning at this point, when we reach our base case, that means that it means that we have 123455 calls taking up space on our call stack, and five also happens to be our value for n.</p>
<p>So that would mean that this function, its space complexity is O of n.</p>
<p>So this function has a space complexity of obon.</p>
<p>So the most important thing to remember here is that all of these recursive calls exist on the call stack simultaneously.</p>
<p>And each one of them takes up memory, which is why, if we have, if we pass in five, two as our n, we'll have five calls existing in memory simultaneously, which means that our space complexity is going to be over then it's going to scale linearly with the size of the input.</p>
<p>So if we increase the size of this input, the space required to execute this function is going to scale proportionally with the size of this input.</p>
<p>So now that we have an understanding of space complexity, we can get into some common mistakes that people make with Bingo.</p>
<p>And the first one being that when you first start out with Viggo, you might see a function that looks like this that has two for loops.</p>
<p>And you might instinctively assume that this function is of all of in square time complexity, because you see that there are two for loops here.</p>
<p>So that must mean observe in square.</p>
<p>But actually, as we've learned O of n square actually means that for each iteration, up until the size of our input, we're going to iterate all the way through an additional for loop up until the size of our input.</p>
<p>So what does it mean if we have two for loops that aren't nested that aren't old in square, it's actually quite simple.</p>
<p>So we have one for loop here, and we have another for loop here.</p>
<p>And as we already know, this for loop would be O of n, time complexity.</p>
<p>And this one would also be O of n.</p>
<p>So this point, we have to all events, so that could easily be translated to o of two in, which is just all of two times in so two times we have all of in.</p>
<p>But if we remember from our previous lessons, we ignore constants.</p>
<p>And in this case, multiplying in by two, two is just a constant.</p>
<p>So we can actually just drop this constant, in which case, this just becomes over then.</p>
<p>But there's one important thing that we need to recognize here.</p>
<p>This is all then because we're iterating through the same input for both of these four loops.</p>
<p>So as long as our loops are acting on the same input, then this would be the resulting complexity.</p>
<p>But there's actually another common mistake that people make when taking time complexity into consideration, which is somewhat related to this mistake.</p>
<p>And this common mistake involves having two separate inputs to the function.</p>
<p>So let's first take this two inputs add function into consideration.</p>
<p>So if you remember from our last example, we only had an input, we only had one input, which was a and the two for loops loop through that same input.</p>
<p>But for this one, you can see that we have two separate inputs here.</p>
<p>So we have an input a, which is loop through in this top four loop.</p>
<p>And we have an input B, which is looped through in this second for loop.</p>
<p>And some people might make the mistake of thinking that this is the same as the last situation where the result would be o of two in.</p>
<p>But this is actually wrong.</p>
<p>Because in this particular situation, we have no way of knowing the difference in size of both of these inputs, like all we know is that these are two separate inputs.</p>
<p>So these two separate inputs could be of either completely different sizes, or they could be of the same size.</p>
<p>But from our analysis perspective, we have no idea.</p>
<p>so in this situation, when we have two inputs, and we have a separate for loop for each input, we're going to need to keep track of both of the inputs.</p>
<p>So in this case, the time that it would take to loop through both these for loops is O of A plus B, because we need to first loop through this one up until we reach the value of a and then we need to loop through this one up until we reach the value of V of B and at this point, this can't be simplified any further we need to acknowledge the fact that both of these inputs are separate inputs.</p>
<p>So this would be over a plus b.</p>
<p>And here we have the similar situation where we have two inputs, but this Time The four loops are nested.</p>
<p>And a lot of people make the mistake of saying that this a function that looks like this is O of n square.</p>
<p>But that would actually be wrong as well, because what does O of n square mean over n square means that for every iteration of one input, we're going to iterate through that same exact input.</p>
<p>But in this situation, when we have a, we have two separate inputs.</p>
<p>For every iteration of one of the inputs, we're going to iterate through the other input.</p>
<p>So what that means is, this is wrong.</p>
<p>In actuality, it's o of A times V, because again, we need to specify that these are two different inputs.</p>
<p>And these inputs could be of different sizes.</p>
<p>And we need to make that visible when we take our complexity into consideration.</p>
<p>So that is space complexity and some common mistakes that people make with big O notation.</p>
<p>I hope that that makes sense. </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Big O Notation Examples – Time Complexity and Algorithm Efficiency Explained ]]>
                </title>
                <description>
                    <![CDATA[ By Jeremy L Thompson Time complexity analysis helps us determine how much more time our algorithm needs to solve a bigger problem.  In this article, I will explain what Big O notation means in time complexity analysis. We'll look at three different a... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/big-o-notation-examples-time-complexity-explained/</link>
                <guid isPermaLink="false">66d45f63e39d8b5612bc0da1</guid>
                
                    <category>
                        <![CDATA[ algorithms ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #big o notation ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 30 Mar 2021 15:50:42 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/60625dd39618b008528a9495.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Jeremy L Thompson</p>
<p>Time complexity analysis helps us determine how much more time our algorithm needs to solve a bigger problem. </p>
<p>In this article, I will explain what Big O notation means in time complexity analysis. We'll look at three different algorithms for checking if a number is prime, and we'll use Big O notation to analyze the time complexity of these algorithms.</p>
<h2 id="heading-what-does-big-o-notation-mean">What does Big O Notation Mean?</h2>
<p>Big O notation describes how an algorithm's estimated runtime increases when we increase the size of the problem we are solving.</p>
<p>Let's consider some hypothetical algorithms for sorting a list of numbers.</p>
<p>If we have an <code>O(n)</code> algorithm for sorting a list, the amount of time we take increases linearly as we increase the size of our list. </p>
<p>A list that has 10 times as many numbers will take approximately 10 times as long to sort. This means that if sorting <code>10</code> numbers takes us <code>4</code> seconds, then we would expect sorting a list of <code>100</code> numbers to take us approximately <code>40</code> seconds.</p>
<p>If we instead have an <code>O(n²)</code> algorithm for sorting a list, then we should expect that the amount of time we take will increase quadratically as we increase the size of our list. </p>
<p>A list that has <code>10</code> times as many numbers will take approximately <code>100</code> times as long to sort! This means that if sorting <code>10</code> numbers takes us <code>4</code> seconds, then we would expect sorting a list of <code>100</code> numbers to take us approximately <code>400</code> seconds.</p>
<p>The fastest algorithms for sorting a list are actually <code>O(n log(n))</code>.<br>With these algorithms, we can expect that a list with <code>10</code> times as many numbers will take approximately <code>23</code> times as long to sort. </p>
<p>In other words, if sorting <code>10</code> numbers takes us <code>4</code> seconds, then we would expect sorting a list of <code>100</code> numbers to take us approximately <code>92</code> seconds.</p>
<h2 id="heading-big-o-example-prime-number-checker">Big O Example - Prime Number Checker</h2>
<p>Now that we know what Big O notation tells us, let's look at how we use Big O notation in time complexity analysis.</p>
<p>In this section, we will look at three different algorithms for checking if a number is <em>prime</em>. By the definition of a <em>prime</em> number, <code>num</code> is <em>prime</em> if the only numbers that divide evenly into <code>num</code> are <code>1</code> and <code>num</code> itself.</p>
<h3 id="heading-algorithm-1-check-all-possible-divisors">Algorithm 1 - Check All Possible Divisors</h3>
<p>The simplest algorithm I can think of to check if a number is <em>prime</em> is to check for any divisors other than <code>1</code> and <code>num</code> itself. </p>
<p>In the <code>is_prime_all()</code> function below, I try dividing every number between <code>1</code> and <code>num</code> into <code>num</code> and check for a remainder.</p>
<p>I wrote this code in Rust so I could use <a target="_blank" href="https://docs.rs/criterion">criterion</a> to benchmark the runtime, but time complexity analysis with Big O notation works the same way with every programming language. </p>
<p>If you want to run criterion with this code on your computer, you can find the Rust source code on <a target="_blank" href="https://github.com/jeremylt/time_complexity">GitHub</a>.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">is_prime_all</span></span>(num: <span class="hljs-built_in">i64</span>) -&gt; <span class="hljs-built_in">bool</span> {
    <span class="hljs-comment">// Check for divisors of num</span>
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-number">2</span>..num {
        <span class="hljs-keyword">if</span> num % i == <span class="hljs-number">0</span> {
            <span class="hljs-comment">// Any divisor other than 1 or num means num is not prime</span>
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }
    }
    <span class="hljs-comment">// No other divisors found means num is prime</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
</code></pre>
<p>We have to check <code>num - 2</code> different numbers with this algorithm before we can say that <code>num</code> is prime, so this algorithm has time complexity of <code>O(num)</code> or <code>O(n)</code>. </p>
<p>You probably noticed that we removed the <code>-2</code> from our Big O notation. When we are calculating the time complexity in Big O notation for an algorithm, we only care about the biggest factor of <code>num</code> in our equation, so all smaller terms are removed.</p>
<p>When I tested my function, it took my computer an average of <code>5.9</code> microseconds to verify that <code>1,789</code> is prime and an average of <code>60.0</code> microseconds to verify that <code>17,903</code> is prime. </p>
<p>This means that it takes approximately <code>10</code> times longer to check a number that is <code>10</code> times larger, which we expected from our time complexity analysis!</p>
<h3 id="heading-algorithm-2-check-half-of-the-possible-divisors">Algorithm 2 - Check Half of the Possible Divisors</h3>
<p>We can improve our algorithm. If our number, <code>num</code>, is not divisible by <code>2</code>, then we also know that our number can't be divisible by <code>num/2</code> or any larger number. This means we can check fewer numbers:</p>
<pre><code class="lang-rust"><span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">is_prime_half</span></span>(num: <span class="hljs-built_in">i64</span>) -&gt; <span class="hljs-built_in">bool</span> {
    <span class="hljs-comment">// Check for divisors of num</span>
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-number">2</span>..num / <span class="hljs-number">2</span> {
        <span class="hljs-keyword">if</span> num % i == <span class="hljs-number">0</span> {
            <span class="hljs-comment">// Any divisor other than 1 or num means num is not prime</span>
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }
    }
    <span class="hljs-comment">// No other divisors found means num is prime</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
</code></pre>
<p>This code takes half as long to run. On my computer, it only takes <code>3.1</code> microseconds to verify that <code>1,789</code> is prime. Unfortunately, it takes <code>31.1</code> microseconds to verify that <code>17,903</code> is prime, which means that the time complexity of our algorithm did not change!</p>
<p>This is because our largest factor of <code>num</code> was the same in the time complexity of our new algorithm. We need to check <code>num/2 - 1</code> values, which means that our algorithm is still <code>O(n)</code>.</p>
<h3 id="heading-algorithm-3-check-all-possible-divisor-pairs">Algorithm 3 - Check all Possible Divisor Pairs</h3>
<p>Let's try a third algorithm and see if we can get a smaller time complexity.</p>
<p>For this algorithm, we will improve upon our second algorithm. In algorithm 2, we use the fact that if <code>2</code> is not a divisor of our number, <code>num</code>, then <code>num/2</code> can't be a divisor either.</p>
<p>But this is not a special trick we can only do with <code>2</code>. If <code>3</code> is not a divisor of our number, then <code>num/3</code> also can't be a divisor. If <code>4</code> is not a divisor of our number, then <code>num/4</code> can't be a divisor either. </p>
<p>The biggest number we need to check is <code>√num</code>, because <code>√num * √num = num.</code></p>
<pre><code class="lang-rust"><span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">is_prime_sqrt</span></span>(num: <span class="hljs-built_in">i64</span>) -&gt; <span class="hljs-built_in">bool</span> {
    <span class="hljs-comment">// Check for divisors of num</span>
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-number">2</span>..=(num <span class="hljs-keyword">as</span> <span class="hljs-built_in">f64</span>).sqrt() <span class="hljs-keyword">as</span> <span class="hljs-built_in">i64</span> {
        <span class="hljs-keyword">if</span> num % i == <span class="hljs-number">0</span> {
            <span class="hljs-comment">// Any divisor other than 1 or num means num is not prime</span>
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }
    }
    <span class="hljs-comment">// No other divisors found means num is prime</span>
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
</code></pre>
<p>We are only checking <code>√num - 1</code> different numbers, so this means that our time complexity should be <code>O(√n)</code>. When I run this on my computer, I see that it takes only <code>161.9</code> nanoseconds to check that <code>1,789</code> is prime and <code>555.0</code> nanoseconds to check that <code>17,903</code> is prime. </p>
<p>This means it took approximately <code>3.4</code> times longer to check a number that is <code>10</code> times larger, and <code>√10 ≈ 3.2</code>. Our complexity analysis accurately estimates how much longer it takes to check bigger prime numbers with this algorithm.</p>
<h2 id="heading-summary">Summary</h2>
<p>Time complexity analysis helps us determine how much more time our algorithm needs to solve a bigger problem. </p>
<p>We looked at what Big O notation means in time complexity analysis, and we used Big O notation to analyze the time complexity of three primality checking algorithms.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How Big O Notation Works – Explained with Cake ]]>
                </title>
                <description>
                    <![CDATA[ Big O notation is used in computer science to define an upper bound of an algorithm. It is mostly used to define the maximum time of an algorithm as a function of the input size, but it can also be used to define memory usage. In this article we will... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/big-o-notation/</link>
                <guid isPermaLink="false">66bb9258867a396452a80284</guid>
                
                    <category>
                        <![CDATA[ algorithms ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #big o notation ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Cedd Burge ]]>
                </dc:creator>
                <pubDate>Mon, 28 Dec 2020 14:54:48 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5fd9038ee6787e098393f598.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Big O notation is used in computer science to define an upper bound of an algorithm. It is mostly used to define the maximum time of an algorithm as a function of the input size, but it can also be used to define memory usage.</p>
<p>In this article we will talk through the most common types of ‘Big O’ notation, using birthday cakes to illustrate the concepts. We'll pretend we're hosting a party, and need to determine how many cakes to bake based on how many people attend.</p>
<h2 id="heading-o1-constant-time">O(1) - Constant Time</h2>
<p>For the Constant Time example, no matter how many people come to the birthday party, you only make one cake. So the cake making time stays constant.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/o-1--constant-time.png" alt="0(1) Constant Time Illustration" width="600" height="400" loading="lazy"></p>
<p>Note that Big O notation doesn’t specify how long the Constant Time is (maybe it takes 1 hour to make the cake, maybe it takes 4 hours). It just states that the time taken doesn’t increase with the number of guests.</p>
<p>A real world example of an O(1) operation is accessing an array by its index. It is just as quick to retrieve an element from a 10 item array as it is to retrieve an element from a 1 million item array.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/o-1--constant-time-grqph.png" alt="0(1) Constant Time Graph" width="600" height="400" loading="lazy"></p>
<h2 id="heading-olog-n-logarithmic-time">O(log n) - Logarithmic Time</h2>
<p>For the Logarithmic Time example, the birthday cakes are used as a way to incentivise people to turn up to the party on time. </p>
<p>The first person to arrive gets a cake all to themselves. Then the next 2 people to arrive share a cake. Then the next 4 people all share a cake, and so on.</p>
<p>So a 1 person party requires 1 cake. A 2 or 3 person party requires 2 cakes. A 4 - 7 person party requires 3 cakes, and a 8 to 15 person party requires 4 cakes. In general an ‘n’ person party requires log<em>2</em>(n) cakes.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/o-log-n--logarithmic-time.png" alt="O(log n) Logarithmic Time Illustration" width="600" height="400" loading="lazy"></p>
<p>The most common real world example of an O(log n) operation is a binary search of an ordered array. </p>
<p>This algorithm looks at the middle of an array and sees if the value is lower or higher than the one it is looking for. Since the list is ordered, it then knows which half of the array the target value is in. </p>
<p>It then repeats the process with that half of the array. So for a 16 item array, the first iteration narrows down the search to 8 items, then 4 then 2 and then 1, for a maximum of 4, or log<em>2</em>(16), iterations over all.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/o-log-n--logarithmic-time-graph.png" alt="O(log n) Logarithmic Time Graph" width="600" height="400" loading="lazy"></p>
<h2 id="heading-on-linear-time">O(n) - Linear Time</h2>
<p>For the Linear Time example, each guest gets their own cake. If ‘n’ people come to the party, you need to make ‘n’ cakes. So the time taken is related to the number of guests. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/o-n--linear-time.png" alt="O(n) Linear Time Illustration" width="600" height="400" loading="lazy"></p>
<p>Again Big O notation doesn’t specify how long the time is (maybe it takes 1 hour to make the cake, maybe it takes 4 hours), it just states that the time increases linearly with the number of guests.</p>
<p>A real world example of an O(n) operation is a naive search for an item in an array. In a 10 item array, at worst you will have to look all 10 items to find the one you want. But for a 1 million item array, you may have to look at all 1 million. </p>
<p>Of course, you might find the solution sooner, but Big O notation specifies the maximum amount of time that an algorithm will take.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/o-n--linear-time-graph.png" alt="O(n) Linear Time Graph" width="600" height="400" loading="lazy"></p>
<h2 id="heading-on2-quadratic-time">O(n^2) - Quadratic Time</h2>
<p>For the Quadratic Time example, each guest gets their own cake. Additionally, each cake has the names of all the guests written on it, with some delicious icing. </p>
<p>In this case a 1 person party has one cake with one name on it. A 2 person party has two cakes, both with two names on (4 names total) and a 3 person party has three cakes, all with three names on them, which is 9 names in total.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/o-n-2--quadratic-time.png" alt="O(n^2) Quadratic Time Illustration" width="600" height="400" loading="lazy"></p>
<p>In general, an ‘n’ person party requires n*n names to be written (also known as n squared, or n to the power of 2), so the speed of making cakes (and writing all the names) is related to the square of the number of guests.</p>
<p>A real world example of an O(n^2) operation is a naive search for duplicates in an array. In this scenario, you loop through all the items in the array, and for each of those items, loop through the array again to see if there are any matches. </p>
<p>For a 10 item array, the outer loop has 10 iterations, and for each of those there are 10 iterations of the inner loop, for 100 in total. For a 1 million item array, it is 1000 billion.</p>
<p>There is a more general case of O(n^2), where instead of the time being relative to n to the power of 2 (n^2), it is relative to n to the power of c (n^c). This is usually called Polynomial Time.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/o-n-2--quadratic-time-graph.png" alt="O(n^2) Quadratic Time Graph" width="600" height="400" loading="lazy"></p>
<h2 id="heading-on-factorial-time">O(n!) - Factorial Time</h2>
<p>For the Factorial Time example, the guests take part in a <a target="_blank" href="https://en.wikipedia.org/wiki/P%C3%A9tanque">Pétanque</a> competition, and the winner takes home the cake. </p>
<p>There is a slight issue, though, in that the player that takes the first turn is at a disadvantage. To help level things out, many games are played, so that each permutation of guests is covered and everyone gets to go first. All these permutations are written on to the cake, again with some tasty icing.</p>
<p>This means that a 2 person party has two games, as each guest takes it in turn to go first. A 3 person party has 6 games (if we imagine that the guests are Anna, Brian and Chris, then the permutations are ABC, ACB, BAC, BCA, CAB, CBA).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/o-n---factorial-time.png" alt="O(n!) - Factorial Time Illustration" width="600" height="400" loading="lazy"></p>
<p>In general, an ‘n’ person party requires n!, or n factorial games, so the speed of making the cake is related to the this. </p>
<p>n! is calculated by multiplying all whole numbers from n down to one “n <em> (n - 1)  </em> (n - 2) … <em> 2 </em> 1”. So for the 2 person party it is 2 <em> 1, or 2. For the three person party it is 3 </em> 2 * 1, which is 6.</p>
<p>Real world examples of O(n!) operations are anything that requires analysing a list of permutations, such as the <a target="_blank" href="https://en.wikipedia.org/wiki/Travelling_salesman_problem">traveling salesman problem</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/image-165.png" alt="O(n!) Factorial Time Graph" width="600" height="400" loading="lazy"></p>
<h2 id="heading-conclusions">Conclusions</h2>
<p>Hopefully the birthday cakes have made ‘Big O’ notation easier to digest! The graph below is also a good memory aid, showing the relative speeds of the algorithms (if there is a choice, then you want the faster one!)</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/image-166.png" alt="Big O Notation graph" width="600" height="400" loading="lazy"></p>
<p>There are quite a few other ‘Big O’ notations, such as O(n log n) and O(c^n) but they all follow the same pattern. If you want to learn more about it, <a target="_blank" href="https://www.freecodecamp.org/news/big-o-notation-why-it-matters-and-why-it-doesnt-1674cfa8a23c/">check out this article</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Combine Good Ideas to Get the Best Solution to a Problem ]]>
                </title>
                <description>
                    <![CDATA[ By Jose J. Rodríguez Trade-offs are present in all our activities. Maybe you have heard the so-called "No free lunch theorem" in Machine Learning. The theorem states that there is no silver bullet when it comes to machine learning models. But in fact... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/combining-good-ideas-to-get-the-best-solution/</link>
                <guid isPermaLink="false">66d45f65706b9fb1c166b987</guid>
                
                    <category>
                        <![CDATA[ algorithms ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #big o notation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Computer Science ]]>
                    </category>
                
                    <category>
                        <![CDATA[ data structures ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Problem Solving ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 30 Nov 2020 23:22:55 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5fc306db49c47664ed82751b.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Jose J. Rodríguez</p>
<p>Trade-offs are present in all our activities. Maybe you have heard the so-called "No free lunch theorem" in Machine Learning. The theorem states that there is no silver bullet when it comes to machine learning models. But in fact, there is never a silver bullet when it comes to anything. </p>
<p>And that is not a Computer Science principle but an economics principle.</p>
<p>If you have written code for more than a month you have likely experienced the necessity of trade-offs in programming – or at least have heard about them. </p>
<p>Sometimes you sacrifice performance in the name of security, security in the name of scalability, beauty and readability in the name of performance, and so on. Don't forget you also probably sacrifice parties and fun in general in the name of programming, so make it worth it.</p>
<p>In the specific case of algorithms, the main resources are time and memory, so the trade-offs always involve those resources. </p>
<p>It's common to find several solutions for the same problem, because one of them is faster but the other one is cheaper when it comes to storage. And of course, there are other factors like implementation, simplicity, and security. </p>
<p>In this post, I'm going to write about how you can combine several solutions to get the one that fulfills all your requirements.</p>
<p>First I'm going to show an interesting idea that E.W. Dijkstra proposed to find prime numbers. After that, I will show an idea I came up with to obtain a data structure that combines the power of arrays and linked lists. </p>
<h2 id="heading-requirements">Requirements</h2>
<p>I assume you have basic programming skills as well as some knowledge about data structures like arrays, linked lists, and heaps.</p>
<p>You should also have some understanding of big-O notation to calculate complexity.</p>
<p>Lastly, it would be good you were familiar with the algorithm called tje Sieve of Eratosthenes. In case you aren't, you can check out this post <a target="_blank" href="https://www.geeksforgeeks.org/sieve-of-eratosthenes/">link</a>.</p>
<p>There is no other previous knowledge required to understand what I'm about to discuss.</p>
<h2 id="heading-two-algorithms-one-problem">Two algorithms, one problem</h2>
<p>The problem is to find all prime numbers from 0 (zero) to N. It's the kind of problem you learn to do when you are beginning your journey in programming. And I want to start with the simplest solution.</p>
<h3 id="heading-the-naive-algorithm">The Naive Algorithm</h3>
<p>In the naive algorithm, we iterate over all numbers <code>x</code> from 2 to N. Then we check whether <code>x</code> has any divisor besides itself and one. For the last step, we can check for every number <code>d</code> between 2 and <code>x - 1</code> whether it is a divisor or not.</p>
<p>There is room for improvement in the last step, because we only need to check the divisors that are less than or equal to the square root of x. A pseudocode of the algorithm is written below:</p>
<pre><code>   primes(N):
      prime_numbers &lt;- [] # empty list
      <span class="hljs-keyword">for</span> x = <span class="hljs-number">2</span> to N:
          is_prime &lt;- <span class="hljs-literal">true</span>
          <span class="hljs-keyword">for</span> d = <span class="hljs-number">2</span> to sqrt(x):
              <span class="hljs-keyword">if</span> d divides x:
                   is_prime &lt;- <span class="hljs-literal">false</span> # we found a divisor <span class="hljs-keyword">of</span> x so x is not a prime number
          <span class="hljs-keyword">if</span> is_prime:
               prime_numbers.add(x) # <span class="hljs-keyword">if</span> we didn<span class="hljs-string">'t find a divisor then x is prime
       return prime_numbers</span>
</code></pre><p>What's the runtime complexity of the previous algorithm? Well, we take every number from 2 to N and for each one, we iterate over all its possible divisors. So we make <code>O(N*sqrt(N))</code> operations, where <code>sqrt</code> stands for the square root function.</p>
<p>What about memory? We only store the primes we find. For a big enough N, the number of primes is relatively small compared with N. So, let's denote the memory complexity as <code>O(Primes(N))</code>, where <code>Primes(N)</code> is the number of primes between 0 and N. Note that this is optimum in terms of memory because we at least need to store all the prime numbers.</p>
<h3 id="heading-the-sieve-of-eratosthenes">The Sieve of Eratosthenes</h3>
<p>You probably already know that there is a better algorithm to find all the prime numbers in a given range: the sieve of Eratosthenes. Let's look at it.</p>
<p>The idea is to maintain an array of length N where every entry is either false or true. If the i-th position in the array is true, then we say that <code>i</code> is a prime number, otherwise <code>i</code> is not prime.</p>
<p>We start with all positions with a true value, and then for every position, starting with <code>i = 2</code>, we make false every multiple of <code>i</code>. </p>
<p>We end up with an array in which every position with a true value represents a prime number. The code with some optimizations is presented below.</p>
<pre><code>    primes(N):
        prime_numbers &lt;- [] # empty list
        sieve &lt;- a boolean list <span class="hljs-keyword">with</span> length equal to N and all its elements set to <span class="hljs-literal">true</span>

        <span class="hljs-keyword">for</span> i = <span class="hljs-number">2</span> to sqrt(N): # we only need to analyze the numbers less than or equal to sqrt(N)
            <span class="hljs-keyword">if</span> sieve[i]: # <span class="hljs-keyword">if</span> sieve[i] is <span class="hljs-literal">true</span> then i is prime
                prime_numbers.add(i)
                <span class="hljs-keyword">for</span> j = i*i to N: # we can start the inner loop <span class="hljs-keyword">in</span> i*i
                    sieve[j] &lt;- <span class="hljs-literal">false</span>
                    j &lt;- j + i
         <span class="hljs-keyword">return</span> prime_numbers
</code></pre><p>It can be proved that the algorithm described above does <a target="_blank" href="https://www.geeksforgeeks.org/how-is-the-time-complexity-of-sieve-of-eratosthenes-is-nloglogn/">O(N log(N))</a> operations which is better than the naive approach. </p>
<p>Even it can be improved to <a target="_blank" href="https://www.geeksforgeeks.org/sieve-eratosthenes-0n-time-complexity">O(N)</a>! But we need to store an array with all the N numbers, and thus, we end up with a more expensive algorithm in memory terms. Here we have the trade-off: time in exchange for memory.</p>
<p>Can we design an algorithm that is faster than the naive idea but cheaper in memory terms than the sieve of Eratosthenes? </p>
<h2 id="heading-dijkstra-and-the-production-line">Dijkstra and the production line</h2>
<p>In the '60s, E.W. Dijkstra wrote in one of his manuscripts [<a target="_blank" href="https://www.cs.utexas.edu/~EWD/ewd02xx/EWD249.PDF">PDF</a>] an algorithm that combines the naive and the sieve ideas. But before we talk about it, let's analyze the differences between the two previous algorithms.  </p>
<p>When applying the naive algorithm we focus on analyzing whether every number is prime or not. When applying the sieve we analyze, for every prime number, all its multiples. The difference can be illustrated with the following analogy.</p>
<p>Imagine we want to build several products, like action figures. We have two options: building them one by one or applying a production line and in every stage, we add one component of the action figure. With the latter, we end up producing more in less time, but we need the "line infrastructure".</p>
<p>Dijkstra combined those ideas by analyzing a number at a time but taking advantage of the previous operations. We can maintain a pool of primes that have already been discovered, and, for each of those prime numbers, store the smallest multiple that has not been analyzed yet. </p>
<p>For example:</p>
<p>If we are analyzing the number 6, then we have stored primes 2, 3, and 5, along with their smallest-not-analyzed-multiples that are: 6 for 2, also 6 for 3, and 10 for 5.</p>
<p>Then, when analyzing a new number we take the smallest multiple stored until that moment and if that multiple is greater than the new number, then we have found a new prime. Otherwise, we have a composite number and we need to update the multiples of the stored primes that have the new number as its smallest multiple.</p>
<p>We begin storing the prime number <code>2</code> with the smallest multiple <code>4</code> as well. Then when analyzing <code>3</code> we find that <code>4 &gt; 3</code> so <code>3</code> is prime. We store <code>3</code> along with its smallest multiple that has not been analyzed yet (<code>6</code>). When analyzing <code>4</code> we find that <code>4</code> is stored as a multiple of <code>2</code>, then we update the multiple of <code>2</code> that now will be <code>6</code>. When analyzing <code>5</code> we find that <code>6 &gt; 5</code> so <code>5</code> is a prime number and we store it along with <code>10</code>, and so on... The code is presented below.</p>
<pre><code>    primes(N):
        primes_pool &lt;- Heap( (<span class="hljs-number">4</span>, <span class="hljs-number">2</span>) ) # a heap that contains a tuple <span class="hljs-keyword">with</span> a prime number (<span class="hljs-number">2</span>) and its least multiple that hasn<span class="hljs-string">'t been analyzed yet (4).
        prime_numbers &lt;- [2] # a list that contains the number 2
        for i = 3 to N:
            tuple &lt;- getMin(primes_pool) # get the tuple with the minimum multiple, the prime number attached to it is irrelevant
            mult &lt;- tuple.first # the multiple is stored in the first position of the tuple
            if mult &gt; i:
                prime_numbers.add(i) # i is prime!
                primes_pool.insert( (i*i, i) ) # we insert i along with their least multiple, but it can be inserted i*i instead and the algorithm remains correct
                continue
            # otherwise i isn'</span>t prime and then...
            for t such that t is <span class="hljs-keyword">in</span> primes_pool and t.first is equal to mult:
                t.first &lt;- t.first + t.second # we update every tuple <span class="hljs-keyword">in</span> the pool that has i <span class="hljs-keyword">as</span> it<span class="hljs-string">'s least multiple
        return prime_numbers</span>
</code></pre><p>The previous method only stores the prime numbers and another number for each of those primes. So we have a memory complexity of <code>O(Primes(N))</code> which is the same as the naive idea. </p>
<p>If we store the prime numbers along with their multiples in a structure like a heap, we get a time complexity of <code>O(N*log N)</code> which is the same as the sieve. So we got what we wanted!</p>
<p>The trick here is that we don't need to mark every multiple of the given prime but just the smallest multiple.</p>
<p>I need to say that this is not a practical idea in the sense that the memory complexity of the sieve of Eratosthenes is not that bad and it is an algorithm that's very easy to implement. </p>
<p>My point is that sometimes, if you have several ideas, each of which cannot be applied because of some flaw, then maybe is a good idea to combine their strengths. This gives you a hybrid solution for your problem. </p>
<p>Dijkstra's Factory of Primes taught me to think that way, although I have never implemented that algorithm in a real-life scenario. </p>
<h2 id="heading-combining-arrays-with-linked-lists">Combining arrays with linked lists</h2>
<p>Arrays are simple structures that allow us to get an element by its index in constant time. But we need <code>O(N)</code> operations to insert or remove an element to/from the array in the worst case, where N is the length of the array.</p>
<p>On the other hand, linked lists are structures composed of nodes. Every node has a reference to the next one, and, in the case of doubly-linked lists, each node also has a reference to the previous one. </p>
<p>In this case, we need <code>O(N)</code> operations to reach a node in the worst case, but the insert and remove operations can be done in constant time. </p>
<p>I think it's natural to think about a "perfect data structure" that allows us to make the three operations in constant time. Sadly, such a structure does not exist, but I have found a middle point between the two opposite poles.</p>
<p>The "issue" with arrays is that they maintain a reference to all of their elements. That allows us to retrieve any element with the same amount of operations no matter where the element is. </p>
<p>But maintaining those references is what makes the insertions and deletions so costly. </p>
<p>When it comes to linked lists we only maintain a reference to the first and the last node and each node has a reference to its neighbors. So, when inserting or removing a new element we only need to change a few references. </p>
<p>But that lack of references is the reason that we have to spend so many operations to get an element in the worst case.</p>
<p>Viewing the problem from that angle, the idea of finding a middle point in the number of references seems natural. What happens if we maintain a reference to <code>sqrt(N)</code> nodes of the linked list instead of referencing just the first and the last element?</p>
<p>That allows us to have a list of length <code>sqrt(N)</code> such that the distance between each of those nodes in the actual list is <code>sqrt(N)</code>. Having that, we can do each operation (index, insertion, and deletion) in <code>O(sqrt(N))</code>.</p>
<p>If you want to see more details about this structure and a Lisp implementation of it, you can do it <a target="_blank" href="https://github.com/josejorgers/indexed-linked-list">here</a>.</p>
<p>I have also written a post about it in my <a target="_blank" href="https://jj.hashnode.dev/combining-good-ideas-to-get-the-best-solution">personal blog</a>.</p>
<h2 id="heading-conclusions">Conclusions</h2>
<p>We have seen two examples of how you can combine existing solutions to get another one that has some of the good qualities of each of them. My purpose was to show you this way of thinking, not just specific examples. </p>
<p>Note that in the case of Dijkstra's idea, we could achieve the time of the fastest solution and the memory complexity of the naive algorithm. In the second example, we just got a middle point, so the fastest solution is still faster and the memory-cheaper solution is still cheaper. </p>
<p>But the new structure is like a decathlon athlete – it is good in every specificity, but not the best in any one of them. </p>
<p>So don't try to find the silver bullet – remember that there's no free lunch. Even Dijkstra's idea has the disadvantage of being harder to implement and to understand.</p>
<p>Hope you have learned something from this post. You can find more content like this in my <a target="_blank" href="https://jj.hashnode.dev">personal blog</a> and by following me on <a target="_blank" href="https://twitter.com/josejorgexl">Twitter</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Big O Notation Explained with Examples ]]>
                </title>
                <description>
                    <![CDATA[ Big O notation is a way to describe the speed or complexity of a given algorithm. If your current project demands a predefined algorithm, it's important to understand how fast or slow it is compared to other options. What is Big O notation and how do... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/big-o-notation-explained-with-examples/</link>
                <guid isPermaLink="false">66c345f942d4db64acf4cbf3</guid>
                
                    <category>
                        <![CDATA[ algorithms ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #big o notation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ data structures ]]>
                    </category>
                
                    <category>
                        <![CDATA[ toothbrush ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sat, 01 Feb 2020 00:00:00 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5f9c9cf0740569d1a4ca3502.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Big O notation is a way to describe the speed or complexity of a given algorithm. If your current project demands a predefined algorithm, it's important to understand how fast or slow it is compared to other options.</p>
<h2 id="heading-what-is-big-o-notation-and-how-does-it-work">What is Big O notation and how does it work?</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/31781171-74c6b48a-b500-11e7-9626-f715b37b10f0.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Simply put, Big O notation tells you the number of operations an algorithm will make. It gets its name from the literal "Big O" in front of the estimated number of operations.</p>
<p>What Big O notation doesn't tell you is the speed of the algorithm in seconds. There are way too many factors that influence the time an algorithm takes to run. Instead, you'll use Big O notation to compare different algorithms by the number of operations they make.</p>
<h3 id="heading-big-o-establishes-a-worst-case-run-time">Big O establishes a worst-case run time</h3>
<p>Imagine that you're a teacher with a student named Jane. You want to find her records, so you use a simple search algorithm to go through your school district's database.</p>
<p>You know that simple search takes O(n) times to run. This means that, in the worst case, you'll have to search through every single record (represented by n) to find Jane's. </p>
<p>But when you run the simple search, you find that Jane's records are the very first entry in the database. You don't have to look at every entry – you found it on your first try.</p>
<p><em>Did this algorithm take O(n) time? Or did it take O(1) time because you found Jane's records on the first try?</em></p>
<p>In this case, 0(1) is the best-case scenario – you were lucky that Jane's records were at the top. But Big O notation focuses on the worst-case scenario, which is 0(n) for simple search. It’s a reassurance that simple search will never be slower than O(n) time.</p>
<h3 id="heading-algorithm-running-times-grow-at-different-rates">Algorithm running times grow at different rates</h3>
<p>Assume that it takes 1 millisecond to check each element in the school district's database.</p>
<p>With simple search, if you have to check 10 entries, it will take 10 ms to run. But with the <em>binary search algorithm</em>, you only have to check 3 elements, which takes 3 ms to run.</p>
<p>In most cases, the list or database you need to search will have hundreds or thousands of elements.</p>
<p>If there are 1 billion elements, using simple search will take up to 1 billion ms, or 11 days. On the other hand, using binary search will take just 32 ms in the worst-case scenario: </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/31781165-723a053c-b500-11e7-937c-7b33db281efe.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Clearly the run times for simple search and binary search don't grow at nearly the same rate. As the list of entries gets larger, binary search takes just a little more time to run. Simple search's run time grows exponentially as the list of entries increases. </p>
<p>This is why knowing how the running time increases in relation to a list size is so important. And this is exactly where Big O notation is so useful.</p>
<h3 id="heading-big-o-notation-shows-the-number-of-operations">Big O notation shows the number of operations</h3>
<p>As mentioned above, Big O notation doesn't show the <em>time</em> an algorithm will run. Instead, it shows the number of operations it will perform. It tells you how fast an algorithm grows and lets you compare it with others.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/31781175-768c208e-b500-11e7-9718-e632d1391e2d.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Here are some common algorithms and their run times in Big O notation:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Big O notation</td><td>Example algorithm</td></tr>
</thead>
<tbody>
<tr>
<td>O(log n)</td><td>Binary search</td></tr>
<tr>
<td>O(n)</td><td>Simple search</td></tr>
<tr>
<td>O(n * log n)</td><td>Quicksort</td></tr>
<tr>
<td>O(n2)</td><td>Selection sort</td></tr>
<tr>
<td>O(n!)</td><td>Traveling salesperson</td></tr>
</tbody>
</table>
</div><p>Now you know enough to be dangerous with Big O notation. Get out there and start comparing algorithms.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is Big O Notation Explained: Space and Time Complexity ]]>
                </title>
                <description>
                    <![CDATA[ By Shen Huang Do you really understand Big O? If so, then this will refresh your understanding before an interview. If not, don’t worry — come and join us for some endeavors in computer science. If you have taken some algorithm related courses, you’v... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/big-o-notation-why-it-matters-and-why-it-doesnt-1674cfa8a23c/</link>
                <guid isPermaLink="false">66d460ee36c45a88f96b7cfb</guid>
                
                    <category>
                        <![CDATA[ algorithms ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #big o notation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Computer Science ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Mathematics ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 16 Jan 2020 17:24:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/06/0_NSxbYAwcC7Qzk7PP.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Shen Huang</p>
<p>Do you really understand Big O? If so, then this will refresh your understanding before an interview. If not, don’t worry — come and join us for some endeavors in computer science.</p>
<p>If you have taken some algorithm related courses, you’ve probably heard of the term <strong>Big O notation</strong>. If you haven’t, we will go over it here, and then get a deeper understanding of what it really is.</p>
<p>Big O notation is one of the most fundamental tools for computer scientists to analyze the cost of an algorithm. It is a good practice for software engineers to understand in-depth as well. </p>
<p>This article is written with the assumption that you have already tackled some code. Also, some in-depth material also requires high-school math fundamentals, and therefore can be a bit less comfortable to total beginners. But if you are ready, let’s get started!</p>
<p>In this article, we will have an in-depth discussion about Big O notation. We will start with an example algorithm to open up our understanding. Then, we will go into the mathematics a little bit to have a formal understanding. After that we will go over some common variations of Big O notation. In the end, we will discuss some of the limitations of Big O in a practical scenario. A table of contents can be found below.</p>
<h3 id="heading-table-of-contents">Table of Contents</h3>
<ol>
<li>What is Big O notation, and why does it matter</li>
<li>Formal Definition of Big O notation</li>
<li>Big O, Little O, Omega &amp; Theta</li>
<li>Complexity Comparison Between Typical Big Os</li>
<li>Time &amp; Space Complexity</li>
<li>Best, Average, Worst, Expected Complexity</li>
<li>Why Big O doesn’t matter</li>
<li>In the end…</li>
</ol>
<p>So let’s get started.</p>
<h3 id="heading-1-what-is-big-o-notation-and-why-does-it-matter">1. What is Big O Notation, and why does it matter</h3>
<blockquote>
<p>“Big O notation is a mathematical notation that describes the limiting behavior of a function when the argument tends towards a particular value or infinity. It is a member of a family of notations invented by Paul Bachmann, Edmund Landau, and others, collectively called Bachmann–Landau notation or asymptotic notation.”  </p>
<p>— Wikipedia’s definition of Big O notation</p>
</blockquote>
<p>In plain words, Big O notation describes the <strong>complexity</strong> of your code using algebraic terms.</p>
<p>To understand what Big O notation is, we can take a look at a typical example, <strong><em>O(n²)</em></strong>, which is usually pronounced <strong><em>“Big O squared”</em></strong>. The letter <strong><em>“n”</em></strong> here represents the <strong>input size</strong>, and the function <strong><em>“g(n) = n²”</em></strong> inside the <strong><em>“O()”</em></strong> gives us an idea of how complex the algorithm is with respect to the input size.</p>
<p>A typical algorithm that has the complexity of O(n²) would be the <strong>selection sort</strong> algorithm. Selection sort is a sorting algorithm that iterates through the list to ensure every element at index <strong><em>i</em></strong> is the <strong><em>ith</em></strong> smallest/largest element of the list. The <strong>CODEPEN</strong> below gives a visual example of it.</p>
<div class="embed-wrapper">
        <iframe width="100%" height="350" src="https://codepen.io/iMultiThinker/embed/yEpRVr" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>
<p>The algorithm can be described by the following code. In order to make sure the <em>ith</em> element is the <em>ith</em> smallest element in the list, this algorithm first iterates through the list with a for loop. Then for every element it uses another for loop to find the smallest element in the remaining part of the list.</p>
<pre><code class="lang-js">SelectionSort(List) {
  <span class="hljs-keyword">for</span>(i <span class="hljs-keyword">from</span> <span class="hljs-number">0</span> to List.Length) {
    SmallestElement = List[i]
    <span class="hljs-keyword">for</span>(j <span class="hljs-keyword">from</span> i to List.Length) {
      <span class="hljs-keyword">if</span>(SmallestElement &gt; List[j]) {
        SmallestElement = List[j]
      }
    }
    Swap(List[i], SmallestElement)
  }
}
</code></pre>
<p>In this scenario, we consider the variable <strong><em>List</em></strong> as the input, thus input size n is the <strong><em>number of elements inside List</em></strong>. Assume the if statement, and the value assignment bounded by the if statement, takes constant time. Then we can find the big O notation for the SelectionSort function by analyzing how many times the statements are executed.</p>
<p>First the inner for loop runs the statements inside n times. And then after <strong><em>i</em></strong> is incremented, the inner for loop runs for n-1 times… …until it runs once, then both of the for loops reach their terminating conditions.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_1ajbPJXjt3z7CofVODlaCw.png" alt="Image" width="600" height="400" loading="lazy">
<em>Selection Sort Loops Illustrated</em></p>
<p>This actually ends up giving us a geometric sum, and with some <a target="_blank" href="https://en.wikipedia.org/wiki/1_%2B_2_%2B_3_%2B_4_%2B_%E2%8B%AF">high-school math</a> we would find that the inner loop will repeat for 1+2 … + n times, which equals n(n-1)/2 times. If we multiply this out, we will end up getting n²/2-n/2.</p>
<p>When we calculate big O notation, we only care about the <strong>dominant terms</strong>, and we do not care about the coefficients. Thus we take the n² as our final big O. We write it as O(n²), which again is pronounced <em>“Big O squared”</em>.</p>
<p>Now you may be wondering, what is this <strong><em>“dominant term”</em></strong> all about? And why do we not care about the coefficients? Don’t worry, we will go over them one by one. It may be a little bit hard to understand at the beginning, but it will all make a lot more sense as you read through the next section.</p>
<h3 id="heading-2-formal-definition-of-big-o-notation">2. Formal Definition of Big O notation</h3>
<p>Once upon a time there was an Indian king who wanted to reward a wise man for his excellence. The wise man asked for nothing but some wheat that would fill up a chess board.</p>
<p>But here were his rules: in the first tile he wants 1 grain of wheat, then 2 on the second tile, then 4 on the next one…each tile on the chess board needed to be filled by double the amount of grains as the previous one. The naïve king agreed without hesitation, thinking it would be a trivial demand to fulfill, until he actually went on and tried it…</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/0_em0jJ2rgj-ZapCef.jpg" alt="Image" width="600" height="400" loading="lazy">
_Wheat and Chess Board, Image from <a target="_blank" href="https://en.wikipedia.org/wiki/Wheat_and_chessboard_problem">Wikipedia</a>_</p>
<p>So how many grains of wheat does the king owe the wise man? We know that a chess board has 8 squares by 8 squares, which totals 64 tiles. So the last tile should have a total of  <strong>2⁶³</strong> grains of wheat. If you do a calculation online, <strong>for the entire chessboard,</strong> you will end up getting <strong>1.8446744*10¹⁹</strong> – that is about 18 followed by 18 zeroes. </p>
<p>Assuming that each grain of wheat weights 0.01 grams, that gives us 184,467,440,737 tons of wheat. And 184 billion tons is quite a lot, isn’t it?</p>
<p>The numbers grow quite fast later for exponential growth don’t they? The same logic goes for computer algorithms. If the required efforts to accomplish a task grow exponentially with respect to the input size, it can end up becoming enormously large.</p>
<p>As we will see in a moment, the growth of 2ⁿ is much faster than n². Now, with n = 64, the square of 64 is 4096. If you add that number to 2⁶⁴, it will be lost outside the significant digits. </p>
<p>This is why, when we look at the growth rate, we only care about the dominant terms. And since we want to analyze the growth with respect to the input size, the coefficients which only multiply the number rather than growing with the input size do not contain useful information.</p>
<p>Below is the formal definition of Big O:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/0_cyqWw3UxODl-wqJi.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>[CSE 373 Slides](https://slideplayer.com/slide/9739625/" rel="noopener) from University of Washington</em></p>
<p>The formal definition is useful when you need to perform a math proof. For example, the time complexity for selection sort can be defined by the function f(n) = n²/2-n/2 as we have discussed in the previous section.</p>
<p>If we allow our function g(n) to be n², we can find a constant c = 1, and a N₀ = 0, and so long as N &gt; N₀, N² will always be greater than N²/2-N/2. We can easily prove this by subtracting N²/2 from both functions, then we can easily see N²/2 &gt; -N/2 to be true when N &gt; 0. Therefore, we can come up with the conclusion that f(n) = O(n²), in the other selection <em>sort is “big O</em> squared”.</p>
<p>You might have noticed a little trick here. That is, if you make g(n) grow super fast, way faster than anything, O(g(n)) will always be great enough. For example, for any polynomial function, you can always be right by saying that they are O(2ⁿ) because 2ⁿ will eventually outgrow any polynomials.</p>
<p>Mathematically, you are right, but generally when we talk about Big O, we want to know the <strong>tight bound</strong> of the function. You will understand this more as you read through the next section.</p>
<p>But before we go, let’s test your understanding with the following question. The answer will be found in later sections so it won’t be a throw away.</p>
<blockquote>
<p><strong>Question:</strong> An image is represented by a 2D array of pixels. If you use a nested for loop to iterate through every pixel (that is, you have a for loop going through all the columns, then another for loop inside to go through all the rows), what is the time complexity of the algorithm when the image is considered as the input?</p>
</blockquote>
<h3 id="heading-3-big-o-little-o-omega-amp-theta">3. Big O, Little O, Omega &amp; Theta</h3>
<blockquote>
<p>Big O: “f(n) is O(g(n))” iff for some constants c and N₀, f(N) ≤ cg(N) for all N &gt; N₀  </p>
<p>Omega: “f(n) is Ω(g(n))” iff for some constants c and N₀, f(N) ≥ cg(N) for all N &gt; N₀  </p>
<p>Theta: “f(n) is Θ(g(n))” iff f(n) is O(g(n)) and f(n) is Ω(g(n))  </p>
<p>Little O: “f(n) is o(g(n))” iff f(n) is O(g(n)) and f(n) is not Θ(g(n))  </p>
<p>—Formal Definition of Big O, Omega, Theta and Little O</p>
</blockquote>
<p>In plain words:</p>
<ul>
<li><strong>Big O (O())</strong> describes the <strong>upper bound</strong> of the complexity.</li>
<li><strong>Omega (Ω())</strong> describes the <strong>lower bound</strong> of the complexity.</li>
<li><strong>Theta (Θ())</strong> describes the <strong>exact bound</strong> of the complexity.</li>
<li><strong>Little O (o())</strong> describes the <strong>upper bound excluding the exact bound</strong>.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_O-dcXbYXojkAPEnDuVZMvA.png" alt="Image" width="600" height="400" loading="lazy">
<em>Relationships between Big O, Little O, Omega &amp; Theta Illustrated</em></p>
<p>For example, the function g(n) = n² + 3n is O(n³), o(n⁴), Θ(n²) and Ω(n). But you would still be right if you say it is Ω(n²) or O(n²).</p>
<p>Generally, when we talk about Big O, what we actually meant is Theta. It is kind of meaningless when you give an upper bound that is way larger than the scope of the analysis. This would be similar to solving inequalities by putting ∞ on the larger side, which will almost always make you right.</p>
<p>But how do we determine which functions are more complex than others? In the next section you will be reading, we will learn that in detail.</p>
<h3 id="heading-4-complexity-comparison-between-typical-big-os">4. Complexity Comparison Between Typical Big Os</h3>
<p>When we are trying to figure out the Big O for a particular function g(n), we only care about the <strong>dominant term</strong> of the function. The dominant term is the term that grows the fastest.</p>
<p>For example, n² grows faster than n, so if we have something like g(n) = n² + 5n + 6, it will be big O(n²). If you have taken some calculus before, this is very similar to the shortcut of finding limits for fractional polynomials, where you only care about the dominant term for numerators and denominators in the end.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/0_MPwgKd4lgXACfuNt.png" alt="Image" width="600" height="400" loading="lazy">
<em>Another way to look at Big O, Image from [Stack Overflow](https://stackoverflow.com/questions/1364444/difference-between-big-o-and-little-o-notation" rel="noopener)</em></p>
<p>But which function grows faster than the others? There are actually quite a few rules.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_KfZYFUT2OKfjekJlCeYvuQ.jpeg" alt="Image" width="600" height="400" loading="lazy">
<em>Complexity Growth Illustration from [Big O Cheatsheet](http://bigocheatsheet.com/" rel="noopener)</em></p>
<h4 id="heading-1-o1-has-the-least-complexity">1. O(1) has the least complexity</h4>
<p>Often called <strong><em>“constant time”</em></strong>, if you can create an algorithm to solve the problem in O(1), you are probably at your best. In some scenarios, the complexity may go beyond O(1), then we can analyze them by finding its O(1/g(n)) counterpart. For example, O(1/n) is more complex than O(1/n²).</p>
<h4 id="heading-2-ologn-is-more-complex-than-o1-but-less-complex-than-polynomials">2. O(log(n)) is more complex than O(1), but less complex than polynomials</h4>
<p>As complexity is often related to divide and conquer algorithms, O(log(n)) is generally a good complexity you can reach for sorting algorithms. O(log(n)) is less complex than O(√n), because the square root function can be considered a polynomial, where the exponent is 0.5.</p>
<h4 id="heading-3-complexity-of-polynomials-increases-as-the-exponent-increases">3. Complexity of polynomials increases as the exponent increases</h4>
<p>For example, O(n⁵) is more complex than O(n⁴). Due to the simplicity of it, we actually went over quite many examples of polynomials in the previous sections.</p>
<h4 id="heading-4-exponentials-have-greater-complexity-than-polynomials-as-long-as-the-coefficients-are-positive-multiples-of-n">4. Exponentials have greater complexity than polynomials as long as the coefficients are positive multiples of n</h4>
<p>O(2ⁿ) is more complex than O(n⁹⁹), but O(2ⁿ) is actually less complex than O(1). We generally take 2 as base for exponentials and logarithms because things tends to be binary in Computer Science, but exponents can be changed by changing the coefficients. If not specified, the base for logarithms is assumed to be 2.</p>
<h4 id="heading-5-factorials-have-greater-complexity-than-exponentials">5. Factorials have greater complexity than exponentials</h4>
<p>If you are interested in the reasoning, look up the <a target="_blank" href="https://en.wikipedia.org/wiki/Gamma_function"><strong>Gamma function</strong></a>, it is an <a target="_blank" href="https://en.wikipedia.org/wiki/Analytic_continuation"><strong>analytic continuation</strong></a> of a factorial. A short proof is that both factorials and exponentials have the same number of multiplications, but the numbers that get multiplied grow for factorials, while remaining constant for exponentials.</p>
<h4 id="heading-6-multiplying-terms">6. Multiplying terms</h4>
<p>When multiplying, the complexity will be greater than the original, but no more than the equivalence of multiplying something that is more complex. For example, O(n <em> log(n)) is more complex than O(n) but less complex than O(n²), because O(n²) = O(n </em> n) and n is more complex than log(n).</p>
<p>To test your understanding, try ranking the following functions from the most complex to the least complex. The solutions with detailed explanations can be found in a later section as you read. Some of them are meant to be tricky and may require some deeper understanding of math. As you get to the solution, you will understand them more.</p>
<blockquote>
<p><strong>Question:</strong> Rank following functions from the most complex to the least complex.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_69bzUpQxBwZFLBimaMe7kQ.png" alt="Image" width="600" height="400" loading="lazy">
<em>Examples taken from [Textbook Problems](https://www.chegg.com/homework-help/questions-and-answers/problem-ask-refresh-knowledge-asymptotic-notations-rank-following-functions-order-growth-f-q23698273" rel="noopener)</em></p>
<blockquote>
<p><strong>Solution to Section 2 Question:</strong>  </p>
<p>It was actually meant to be a trick question to test your understanding. The question tries to make you answer O(n²) because there is a nested for loop. However, n is supposed to be the input size. Since the image array is the input, and every pixel was iterated through only once, the answer is actually O(n). The next section will go over more examples like this one.</p>
</blockquote>
<h3 id="heading-5-time-amp-space-complexity">5. Time &amp; Space Complexity</h3>
<p>So far, we have only been discussing the time complexity of the algorithms. That is, we only care about how much time it takes for the program to complete the task. What also matters is the space the program takes to complete the task. The space complexity is related to how much memory the program will use, and therefore is also an important factor to analyze.</p>
<p>The space complexity works similarly to time complexity. For example, selection sort has a space complexity of O(1), because it only stores one minimum value and its index for comparison, the maximum space used does not increase with the input size.</p>
<p>Some algorithms, such as bucket sort, have a space complexity of O(n), but are able to chop down the time complexity to O(1). Bucket sort sorts the array by creating a sorted list of all the possible elements in the array, then increments the count whenever the element is encountered. In the end the sorted array will be the sorted list elements repeated by their counts.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_GfLWx2TXS55unwqZ5-X26w.png" alt="Image" width="600" height="400" loading="lazy">
<em>Bucket Sort Visualization</em></p>
<h3 id="heading-6-best-average-worst-expected-complexity">6. Best, Average, Worst, Expected Complexity</h3>
<p>The complexity can also be analyzed as best case, worst case, average case and expected case.</p>
<p>Let’s take <strong>insertion sort,</strong> for example. Insertion sort iterates through all the elements in the list. If the element is smaller than its previous element, it inserts the element backwards until it is larger than the previous element.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/0*C9ork5K0ay7_CLBv.gif" alt="Image" width="300" height="180" loading="lazy">
_Insertion Sort Illustrated, Image from [Wikipedia](https://en.wikipedia.org/wiki/Insertion_sort" rel="noopener" target="<em>blank" title=")</em></p>
<p>If the array is initially sorted, no swap will be made. The algorithm will just iterate through the array once, which results a time complexity of O(n). Therefore, we would say that the <strong>best-case</strong> time complexity of insertion sort is O(n). A complexity of O(n) is also often called <strong>linear complexity</strong>.</p>
<p>Sometimes an algorithm just has bad luck. Quick sort, for example, will have to go through the list in O(n) time if the elements are sorted in the opposite order, but on average it sorts the array in O(n <em> log(n)) time. Generally, when we evaluate time complexity of an algorithm, we look at their <em>*worst-case</em></em> performance. More on that and quick sort will be discussed in the next section as you read.</p>
<p>The average case complexity describes the expected performance of the algorithm. Sometimes involves calculating the probability of each scenarios. It can get complicated to go into the details and therefore not discussed in this article. Below is a cheat-sheet on the time and space complexity of typical algorithms.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/0_XZsrnwao98R3dGTB.png" alt="Image" width="600" height="400" loading="lazy">
<em>[Big O Cheatsheet](http://bigocheatsheet.com/" rel="noopener) for Common Algorithms</em></p>
<blockquote>
<p><strong>Solution to Section 4 Question:</strong></p>
</blockquote>
<p>By inspecting the functions, we should be able to immediately rank the following polynomials from most complex to least complex with rule 3. Where the square root of n is just n to the power of 0.5.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_RKlbisO36urUbi237TjyrQ.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Then by applying rules 2 and 6, we will get the following. Base 3 log can be converted to base 2 with <strong><a target="_blank" href="https://www.purplemath.com/modules/logrules5.htm">log base conversions</a></strong>. Base 3 log still grows a little bit slower then base 2 logs, and therefore gets ranked after.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_6R1jrWMGXpKxBqtEre9q8Q.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The rest may look a little bit tricky, but let’s try to unveil their true faces and see where we can put them.</p>
<p>First of all, 2 to the power of 2 to the power of n is greater than 2 to the power of n, and the +1 spices it up even more.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_eGLwpHDUJtr6CuALrpcQ2w.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>And then since we know 2 to the power of log(n) with based 2 is equal to n, we can convert the following. The log with 0.001 as exponent grows a little bit more than constants, but less than almost anything else.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_4yo7najRBY_OaTnDpT3cIg.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The one with n to the power of log(log(n)) is actually a variation of the <a target="_blank" href="https://en.wikipedia.org/wiki/Time_complexity#Quasi-polynomial_time"><strong>quasi-polynomial</strong></a>, which is greater than polynomial but less than exponential. Since log(n) grows slower than n, the complexity of it is a bit less. The one with the inverse log converges to constant, as 1/log(n) diverges to infinity.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_ZYUCFuiSbOibqdSfmuwdvA.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The factorials can be represented by multiplications, and thus can be converted to additions outside the logarithmic function. The “n choose 2” can be converted into a polynomial with a cubic term being the largest.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_cbrjlMGsWYCs36u831pLTA.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>And finally, we can rank the functions from the most complex to the least complex.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_NHVggTVMGjGOe7SxtSgIpQ.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-why-bigo-doesnt-matter">Why BigO doesn’t matter</h3>
<blockquote>
<p><strong>!!! — WARNING — !!!</strong>  </p>
<p>Contents discussed here are generally <strong>not accepted</strong> by most programmers in the world. Discuss it <strong>at your own risk</strong> in an interview. People actually blogged about how they <strong>failed</strong> their Google interviews because they questioned the authority, like here.  </p>
<p><strong>!!! — WARNING — !!!</strong></p>
</blockquote>
<p>Since we have previously learned that the worst case time complexity for quick sort is O(n²), but O(n * log(n)) for merge sort, merge sort should be faster — right? Well you probably have guessed that the answer is false. The algorithms are just wired up in a way that makes quick sort the <em>“quick sort”</em>.</p>
<p>To demonstrate, check out this <a target="_blank" href="https://trinket.io/python/87a3166026">trinket.io</a> I made. It compares the time for quick sort and merge sort. I have only managed to test it on arrays with a length up to 10000, but as you can see so far, the time for merge sort grows faster than quick sort. Despite quick sort having a worse case complexity of O(n²), the likelihood of that is really low. When it comes to the increase in speed quick sort has over merge sort bounded by the O(n * log(n)) complexity, quick sort ends up with a better performance in average.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_UvDTlLjNnQurODtnCWjEJg.png" alt="Image" width="600" height="400" loading="lazy">
<em>Time Comparison between Quick Sort &amp; Merge Sort</em></p>
<p>I have also made the below graph to compare the ratio between the time they take, as it is hard to see them at lower values. And as you can see, the percentage time taken for quick sort is in a descending order.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/1_Zdm_8c-uU5941r7zJd4FPQ.png" alt="Image" width="600" height="400" loading="lazy">
<em>Time Ratio between Quick Sort &amp; Merge Sort</em></p>
<p>The moral of the story is, Big O notation is only a mathematical analysis to provide a reference on the resources consumed by the algorithm. Practically, the results may be different. But it is generally a good practice trying to chop down the complexity of our algorithms, until we run into a case where we know what we are doing.</p>
<h3 id="heading-in-the-end">In the end…</h3>
<p>I like coding, learning new things and sharing them with the community. If there is anything in which you are particularly interested, please let me know. I generally write on web design, software architecture, mathematics and data science. You can find some great articles I have written before if you are interested in any of the topics above.</p>
<p>Hope you have a great time learning computer science!!!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is Big Omega Notation? ]]>
                </title>
                <description>
                    <![CDATA[ Similar to big O notation, big Omega(Ω) function is used in computer science to describe the performance or complexity of an algorithm. If a running time is Ω(f(n)), then for large enough n, the running time is at least k⋅f(n) for some constant k. He... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/big-omega-notation/</link>
                <guid isPermaLink="false">66c345fc160da468ed76f15d</guid>
                
                    <category>
                        <![CDATA[ algorithms ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #big o notation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Computer Science ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sat, 04 Jan 2020 00:32:00 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5f9c9e37740569d1a4ca3bf6.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Similar to <a target="_blank" href="https://guide.freecodecamp.org/computer-science/notation/big-o-notation">big O</a> notation, big Omega(Ω) function is used in computer science to describe the performance or complexity of an algorithm.</p>
<p>If a running time is Ω(f(n)), then for large enough n, the running time is at least k⋅f(n) for some constant k. Here’s how to think of a running time that is Ω(f(n)):  </p>
<p><img src="https://s3.amazonaws.com/ka-cs-algorithms/Omega_fn.png" alt="big-omega function" width="361" height="172" loading="lazy"></p>
<p>We say that the running time is “big-Ω of f(n).” We use big-Ω notation for <strong>asymptotic lower bounds</strong>, since it bounds the growth of the running time from below for large enough input sizes.</p>
<h3 id="heading-difference-between-big-o-and-big-w"><strong>Difference between Big O and Big Ω</strong></h3>
<p>The difference between Big O notation and Big Ω notation is that Big O is used to describe the worst case running time for an algorithm. But, Big Ω notation, on the other hand, is used to describe the best case running time for a given algorithm.</p>
<h4 id="heading-more-information"><strong>More Information:</strong></h4>
<ul>
<li><a target="_blank" href="https://www.khanacademy.org/computing/computer-science/algorithms/asymptotic-notation/a/big-big-omega-notation">Big-Ω (Big-Omega) notation</a></li>
</ul>
<p><img src="http://img.youtube.com/vi/OpebHLAf99Y/0.jpg" alt="MYCODSCHOOL Time complexity analysis" width="480" height="360" loading="lazy"></p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
