<?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" version="2.0"><channel><title><![CDATA[Apresh Agarwal's Blog]]></title><description><![CDATA[Apresh Agarwal's Blog]]></description><link>https://blog.apreshagarwal.com</link><generator>RSS for Node</generator><lastBuildDate>Wed, 13 May 2026 19:12:02 GMT</lastBuildDate><atom:link href="https://blog.apreshagarwal.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How Algorithmic Thinking Improves Problem Solving]]></title><description><![CDATA[What is an Algorithm?
In the simplest form, an algorithm is a well-defined, finite sequence of unambiguous instructions designed to solve a problem for a given set of inputs.
Understanding how different algorithms to the same problem work
Let's take ...]]></description><link>https://blog.apreshagarwal.com/how-algorithmic-thinking-improves-problem-solving</link><guid isPermaLink="true">https://blog.apreshagarwal.com/how-algorithmic-thinking-improves-problem-solving</guid><category><![CDATA[algorithms]]></category><category><![CDATA[Algorithmic thinking]]></category><category><![CDATA[Problem Solving]]></category><category><![CDATA[Computer Science]]></category><category><![CDATA[programming fundamentals]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[Coding Concepts]]></category><category><![CDATA[learning to code]]></category><category><![CDATA[logic-and-reasoning]]></category><dc:creator><![CDATA[Apresh Agarwal]]></dc:creator><pubDate>Sun, 20 Feb 2022 19:35:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/WD7S-Lz12Es/upload/92de14bbe7931c06a693b008242f1f60.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-is-an-algorithm">What is an Algorithm?</h2>
<p>In the simplest form, an <em>algorithm</em> is a well-defined, finite sequence of unambiguous instructions designed to solve a problem for a given set of inputs.</p>
<h2 id="heading-understanding-how-different-algorithms-to-the-same-problem-work">Understanding how different algorithms to the same problem work</h2>
<p>Let's take an example.</p>
<p>Suppose Alice, Bob and Charlie are three Software Engineers working at <em>Foogle</em>. Dan, the Team Lead, asked them to come up with an algorithm to find the factors of a positive number N.</p>
<blockquote>
<p>A natural number X is said to be a factor of number N when X completely divides N without leaving any remainder.</p>
</blockquote>
<p>All three come up with different solutions to Dan’s problem. Let us understand each approach and compare them based on the number of checks performed and their approximate runtime.</p>
<h3 id="heading-alices-algorithm">Alice's Algorithm</h3>
<p>Alice approaches the problem using a straightforward brute-force method. She declares a variable <code>factors</code> and initializes it to 0. She then iterates through the entire range <strong>[1, N]</strong> and checks whether each number divides N evenly. If it does, she increments the factor count.</p>
<pre><code class="lang-plaintext">func findFactors(var N) {
    var factors = 0;
    var i = 1;

    while ( i &lt;= N) {
        if (N % i == 0)
            factors = factors + 1;
        i = i + 1;
    }

    return factors;
}
</code></pre>
<p>This approach is simple and easy to understand, but it performs a large number of checks, especially for big values of N.</p>
<h3 id="heading-bobs-algorithm">Bob's Algorithm</h3>
<p>Bob makes the following observations:</p>
<ol>
<li>For any number N, both 1 and N are always factors of N.</li>
<li>For any number N, there are no factors strictly between <strong>N/2</strong> and <strong>N</strong>. For example, if N = 24, no number between 13 and 23 divides 24 evenly.</li>
</ol>
<p>Based on this, Bob initializes factors to 2 and iterates only through the range <strong>[2, N/2]</strong></p>
<pre><code class="lang-plaintext">func findFactors(var N) {
    var factors = 2;
    var i = 2;

    while ( i &lt;= N/2) {
        if (N % i == 0)
            factors = factors + 1;
        i = i + 1;
    }

    return factors;
}
</code></pre>
<p>Bob’s algorithm performs fewer checks than Alice’s approach. However, as the value of N increases, the total number of checks Bob performs still increases steadily with N. While this optimization improves performance in practice, it does not fundamentally change how the amount of work grows as the input becomes larger.</p>
<h3 id="heading-charlies-algorithm">Charlie's Algorithm</h3>
<p>While Alice and Bob focused on reducing unnecessary checks, Charlie approached the problem from a different angle. Instead of checking fewer numbers within the same range, Charlie looked for a mathematical property that could significantly reduce the range itself.</p>
<p>He observed that for any number N, there exists a pair of numbers A and B such that: <strong>A x B = N.</strong></p>
<p>By the associative property of multiplication, <strong>B × A = N</strong> also holds.</p>
<blockquote>
<p>The associative property states that the way numbers are grouped in a multiplication operation does not change the product.</p>
</blockquote>
<p>Charlie used this insight to design an algorithm that only checks values of A up to the square root of N. <em>Whenever A divides N evenly, B = N / A automatically becomes another factor.</em></p>
<p>However, Charlie also accounted for a special case. If <strong>A × A = N</strong>, then N is a perfect square and the factor A should only be counted once.</p>
<p><strong>Example 1: When N = 24 (Not a perfect square)</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>A x B = N</td><td>B x A = N</td><td>Factors</td></tr>
</thead>
<tbody>
<tr>
<td>01 x 24 = 24</td><td>24 x 01 = 24</td><td>0 + 2 = 2</td></tr>
<tr>
<td>02 x 12 = 24</td><td>12 x 02 = 24</td><td>2 + 2 = 4</td></tr>
<tr>
<td>03 x 08 = 24</td><td>08 x 03 = 24</td><td>4 + 2 = 6</td></tr>
<tr>
<td>04 x 06 = 24</td><td>06 x 04 = 24</td><td>6 + 2 = 8</td></tr>
</tbody>
</table>
</div><p><strong>Example 2: When N = 100 (Perfect square)</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>A x B = N</td><td>B x A = N</td><td>Factors</td></tr>
</thead>
<tbody>
<tr>
<td>01 x 100 = 100</td><td>100 x 01 = 100</td><td>0 + 2 = 2</td></tr>
<tr>
<td>02 x 50 = 100</td><td>50 x 02 = 100</td><td>2 + 2 = 4</td></tr>
<tr>
<td>04 x 25 = 100</td><td>25 x 04 = 100</td><td>4 + 2 = 6</td></tr>
<tr>
<td>05 x 20 = 100</td><td>20 x 05 = 100</td><td>6 + 2 = 8</td></tr>
<tr>
<td>10 x 10 = 100</td><td>10 x 10 = 100</td><td><mark>6 + 1 = 9</mark></td></tr>
</tbody>
</table>
</div><pre><code class="lang-plaintext">func findFactors(var N) {
    var factors = 0;
    var sqrt = squareRoot(N);
    var i = 1;

    while ( i &lt;= sqrt) {
        if (N % i == 0)
            if (N/i == i)
                factors = factors + 1;
            else
                factors = factors + 2;
        i = i + 1;
    }

    return factors;
}
</code></pre>
<p>Unlike the previous approaches, Charlie’s method changes how the number of operations grows as the input increases. This difference becomes especially important for very large values of N. In the next part, we will introduce a formal way to describe and compare this growth using time complexity.</p>
<h2 id="heading-analysis-of-the-algorithms">Analysis of the Algorithms</h2>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">The following estimates are simplified and meant to show how different approaches scale as the input grows, not to represent exact execution times.</div>
</div>

<p>Alice, Bob, and Charlie submitted their solutions for review. Dan analyzed each one and chose to merge Charlie’s solution while declining the others.</p>
<p>For comparison, assume a computer with a clock speed of 1 GHz, meaning it can execute approximately 10⁹ instructions per second.</p>
<p>Assume <em>N = 1,000,000,000</em> (or 10⁹). Then:</p>
<ol>
<li><p>Alice's algorithm checks all numbers from 1 to 10⁹. Estimated time = 10⁹ ÷ 10⁹ = <strong>1 second</strong></p>
</li>
<li><p>Bob’s algorithm checks numbers from 1 to 10⁹ ÷ 2. Estimated time = (10⁹ ÷ 2) ÷ 10⁹ = <strong>0.5 seconds</strong></p>
</li>
<li><p>Charlie’s algorithm checks numbers only up to √10⁹. Estimated time ≈ √10⁹ ÷ 10⁹ ≈ <strong>31.62 microseconds</strong></p>
</li>
</ol>
<p>Let's see how each of these algorithms would perform for even larger values of N:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Algorithm</td><td>N = 10¹⁰</td><td>N = 10¹⁵</td><td>N = 10²⁰</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Alice's Algorithm (N iterations)</strong></td><td>10 seconds</td><td>10⁶ seconds ≈ 12 days</td><td>10¹¹ seconds ≈ 3169 years</td></tr>
<tr>
<td><strong>Bob's Algorithm (N÷2 iterations)</strong></td><td>5 seconds</td><td>5 x 10⁵ seconds ≈ 6 days</td><td>5 x 10¹⁰ seconds ≈ 1585 years</td></tr>
<tr>
<td><strong>Charlie's Algorithm (√N iterations)</strong></td><td>0.001 seconds</td><td>0.0316 seconds</td><td>10 seconds</td></tr>
</tbody>
</table>
</div><h2 id="heading-conclusion">Conclusion</h2>
<p>In this example, we compared different approaches to solving the same problem and saw why choosing an efficient algorithm matters.</p>
<p>The runtime of a function can depend on many factors, such as the programming language, system architecture, and hardware performance. Because of this, we need ways to compare algorithms independently of the system on which they run. While execution time may vary across machines, the number of steps performed by an algorithm for a given input remains consistent.</p>
<p>In upcoming blogs, we will introduce two widely used metrics for analyzing algorithm efficiency: Time Complexity and Space Complexity.</p>
<p>Stay tuned, and thank you for reading.</p>
]]></content:encoded></item></channel></rss>