<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Avinash Tyagi</title>
    <description>The latest articles on Forem by Avinash Tyagi (@avinash_tyagi_98).</description>
    <link>https://forem.com/avinash_tyagi_98</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3897290%2F417790ad-95fd-4c33-b67a-1389c58f57ac.png</url>
      <title>Forem: Avinash Tyagi</title>
      <link>https://forem.com/avinash_tyagi_98</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/avinash_tyagi_98"/>
    <language>en</language>
    <item>
      <title>I Knew BFS Existed but Couldn't Wire It Into a Grid Problem</title>
      <dc:creator>Avinash Tyagi</dc:creator>
      <pubDate>Mon, 27 Apr 2026 12:57:54 +0000</pubDate>
      <link>https://forem.com/avinash_tyagi_98/i-knew-bfs-existed-but-couldnt-wire-it-into-a-grid-problem-1l65</link>
      <guid>https://forem.com/avinash_tyagi_98/i-knew-bfs-existed-but-couldnt-wire-it-into-a-grid-problem-1l65</guid>
      <description>&lt;p&gt;I've been working through coding problems that I can understand when reading solutions but struggle to solve on my own. This series is about breaking down the "why does this actually work" part. Not tutorials. Just honest breakdowns of where my thinking got stuck and what finally unstuck it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;LeetCode 1091: Shortest Path in Binary Matrix. You get an n×n grid of 0s and 1s. Find the shortest path from the top-left corner to the bottom-right corner, moving through cells with value 0. You can move in all 8 directions (up, down, left, right, and all four diagonals). Return the number of cells in the path, or -1 if no path exists.&lt;/p&gt;

&lt;p&gt;I looked at this and thought: okay, at every cell that's a 0, I check the connected neighbors. Skip anything out of bounds or already visited. Straightforward enough.&lt;/p&gt;

&lt;p&gt;But when I actually sat down to write it, two things tripped me up. I didn't know how to pick BFS over DFS for this. And even after choosing BFS, I had no idea how to track the path length.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Shortest" is doing the work
&lt;/h2&gt;

&lt;p&gt;My first instinct was just "explore the neighbors." I hadn't committed to BFS or DFS. Both would find &lt;em&gt;a&lt;/em&gt; path. But the problem says &lt;em&gt;shortest&lt;/em&gt;, and that word changes everything.&lt;/p&gt;

&lt;p&gt;DFS goes deep. It picks one direction and follows it as far as possible before backtracking. It'll find a path, but it might be a terrible one. To guarantee the shortest, you'd have to explore every possible path and compare. That's expensive.&lt;/p&gt;

&lt;p&gt;BFS explores in waves. Wave 1 is everything 1 step from the start. Wave 2 is everything 2 steps away. Wave 3, everything 3 steps. The first time you reach the destination, you got there in the fewest steps possible. You never need to compare alternatives because you tried all shorter routes first.&lt;/p&gt;

&lt;p&gt;That's the core insight: BFS gives you shortest path for free. You don't need extra logic to compare paths or track the minimum. The structure of the algorithm handles it.&lt;/p&gt;

&lt;h2&gt;
  
  
  "How do I track the distance though?"
&lt;/h2&gt;

&lt;p&gt;This was my actual sticking point. I had a queue. I was popping cells and pushing neighbors. The BFS loop worked. But I couldn't figure out where the path length came from.&lt;/p&gt;

&lt;p&gt;My queue held coordinates like &lt;code&gt;[i, j]&lt;/code&gt;. When I reached the bottom-right corner, I had no way to know how many steps it took to get there.&lt;/p&gt;

&lt;p&gt;The fix is simple once you see it: store the distance &lt;em&gt;with each item&lt;/em&gt; in the queue. Instead of &lt;code&gt;[i, j]&lt;/code&gt;, push &lt;code&gt;[i, j, distance]&lt;/code&gt;. The start cell goes in as &lt;code&gt;[0, 0, 1]&lt;/code&gt; (distance 1 because the problem counts the start cell). When you process a cell at distance &lt;code&gt;d&lt;/code&gt; and add its neighbors, each neighbor gets &lt;code&gt;d + 1&lt;/code&gt;. When you pop the destination cell, its distance is your answer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# row, col, distance
&lt;/span&gt;
&lt;span class="c1"&gt;# later, when adding neighbors:
&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;ni&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;curr_distance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The distance piggybacks on the BFS traversal. No separate tracking needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full solution
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;deque&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Solution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;shortestPathBinaryMatrix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;

        &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;deque&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# mark visited
&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;curr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popleft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt;

            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;di&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;dj&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;di&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;dj&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;continue&lt;/span&gt;
                    &lt;span class="n"&gt;ni&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;di&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dj&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ni&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;ni&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;nj&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;nj&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ni&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;nj&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                            &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;ni&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
                            &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ni&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;nj&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Walk through a small grid to see the waves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[0, 0, 1]
[0, 1, 0]
[0, 0, 0]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wave 1 (distance 1): &lt;code&gt;(0,0)&lt;/code&gt;.&lt;br&gt;
Wave 2 (distance 2): &lt;code&gt;(0,1)&lt;/code&gt;, &lt;code&gt;(1,0)&lt;/code&gt;.&lt;br&gt;
Wave 3 (distance 3): &lt;code&gt;(2,0)&lt;/code&gt;, &lt;code&gt;(2,1)&lt;/code&gt;.&lt;br&gt;
Wave 4 (distance 4): &lt;code&gt;(2,2)&lt;/code&gt;, &lt;code&gt;(1,2)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;BFS reaches &lt;code&gt;(2,2)&lt;/code&gt; at distance 4. That's the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistakes I made that are probably common
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Counting edges instead of cells.&lt;/strong&gt; I initially set the starting distance to 0. For the grid above, that gives 3 instead of 4. The LeetCode problem counts cells visited (both endpoints), not hops between them. This is the kind of off-by-one that makes you fail test cases and stare at correct-looking code for ten minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Marking cells as visited when popping, not when enqueueing.&lt;/strong&gt; This is subtle. Say cell &lt;code&gt;(1,1)&lt;/code&gt; is a neighbor of both &lt;code&gt;(0,0)&lt;/code&gt; and &lt;code&gt;(0,1)&lt;/code&gt;. If you process &lt;code&gt;(0,0)&lt;/code&gt; first and add &lt;code&gt;(1,1)&lt;/code&gt; to the queue but don't mark it visited yet, then when you process &lt;code&gt;(0,1)&lt;/code&gt;, you add &lt;code&gt;(1,1)&lt;/code&gt; again. Now it's in the queue twice, and all its neighbors get processed twice too. The fix: mark a cell visited the moment you add it to the queue, not when you pop it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Off-by-one on the destination check.&lt;/strong&gt; If the grid is 5×5, the bottom-right is &lt;code&gt;(4,4)&lt;/code&gt;, not &lt;code&gt;(5,5)&lt;/code&gt;. Checking &lt;code&gt;i == len(grid)&lt;/code&gt; instead of &lt;code&gt;i == len(grid) - 1&lt;/code&gt; means you never find the destination. Obvious in hindsight. Not obvious at 11pm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using &lt;code&gt;list.pop(0)&lt;/code&gt; instead of &lt;code&gt;collections.deque&lt;/code&gt;.&lt;/strong&gt; A regular Python list pops from the front in O(n) because it shifts every remaining element. On a big grid, this quietly turns your O(n) BFS into O(n²). &lt;code&gt;deque.popleft()&lt;/code&gt; is O(1). Small change, big difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  When BFS works (and when it doesn't)
&lt;/h2&gt;

&lt;p&gt;BFS gives shortest path when all edges have the same weight. In a grid where every step costs 1, that holds. If some steps cost more than others (weighted graph), BFS won't work. You'd need Dijkstra's algorithm instead.&lt;/p&gt;

&lt;p&gt;The BFS template itself stays remarkably stable across problems. Once you have the loop, the only things that change are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where you start.&lt;/strong&gt; One cell? Multiple cells at once? Every cell of a certain type? (Multi-source BFS seeds the queue with all starting points at distance 0.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where you stop.&lt;/strong&gt; Reached a specific cell? All targets converted? Queue is empty?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which directions.&lt;/strong&gt; 4-directional (up/down/left/right) or 8-directional (including diagonals)?&lt;/p&gt;

&lt;p&gt;The loop, the queue, the visited tracking, the distance propagation: all identical every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice problems
&lt;/h2&gt;

&lt;p&gt;In order of difficulty, all using BFS on grids:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;LeetCode 994 (Rotting Oranges)&lt;/strong&gt;: Multi-source BFS. All rotten oranges start spreading simultaneously. Same wave-by-wave expansion, but you seed the queue with every rotten cell instead of one corner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LeetCode 542 (01 Matrix)&lt;/strong&gt;: Find the shortest distance from every cell to the nearest 0. Multi-source BFS starting from all 0-cells.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LeetCode 1162 (As Far from Land as Possible)&lt;/strong&gt;: Multi-source BFS from all land cells. The answer is the last wave number. Same pattern, different framing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LeetCode 752 (Open the Lock)&lt;/strong&gt;: BFS on a non-grid graph. Each "node" is a 4-digit lock combination. Neighbors are one-digit turns. Tests whether you see that BFS works on any graph, not just grids.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LeetCode 127 (Word Ladder)&lt;/strong&gt;: BFS on words where neighbors differ by one letter. Harder to see the graph structure, but the BFS mechanics are identical.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>algorithms</category>
      <category>dsa</category>
      <category>coding</category>
      <category>leetcode</category>
    </item>
    <item>
      <title>Why I Stopped Trying to Count "Exactly Right" and Started Subtracting</title>
      <dc:creator>Avinash Tyagi</dc:creator>
      <pubDate>Sun, 26 Apr 2026 10:21:20 +0000</pubDate>
      <link>https://forem.com/avinash_tyagi_98/why-i-stopped-trying-to-count-exactly-right-and-started-subtracting-19c3</link>
      <guid>https://forem.com/avinash_tyagi_98/why-i-stopped-trying-to-count-exactly-right-and-started-subtracting-19c3</guid>
      <description>&lt;p&gt;I've been working through coding problems that I can understand when reading solutions but struggle to solve on my own. This series is about breaking down the "why does this actually work" part. Not tutorials. Just honest breakdowns of where my thinking got stuck and what finally unstuck it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem that broke my brain
&lt;/h2&gt;

&lt;p&gt;LeetCode 795: Number of Subarrays with Bounded Maximum. You get an array of integers and a range &lt;code&gt;[left, right]&lt;/code&gt;. Count how many contiguous subarrays have their maximum element within that range.&lt;/p&gt;

&lt;p&gt;I understood what the problem was asking. I could verify answers by hand. But when I sat down to code it, I kept getting tangled. My sliding window logic was a mess because every element fell into one of three buckets: too small (below &lt;code&gt;left&lt;/code&gt;), just right (in the range), or too big (above &lt;code&gt;right&lt;/code&gt;). Each bucket needed different handling, and my code reflected that confusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three zones, two problems
&lt;/h2&gt;

&lt;p&gt;Here's what made this problem feel harder than it should be. When an element is bigger than &lt;code&gt;right&lt;/code&gt;, it's a hard wall. No valid subarray can include it. You reset everything.&lt;/p&gt;

&lt;p&gt;But when an element is smaller than &lt;code&gt;left&lt;/code&gt;? It's fine. It just can't be the maximum. A subarray like &lt;code&gt;[2, 1, 1]&lt;/code&gt; with &lt;code&gt;left=2, right=3&lt;/code&gt; is perfectly valid because the max is 2. The small elements are just passengers.&lt;/p&gt;

&lt;p&gt;So "not in range" actually means two completely different things depending on which side you fall on. Trying to handle both cases in one pass meant tracking which elements were carrying the subarray (in-range elements) separately from which elements were just tagging along (too-small elements). Every &lt;code&gt;if/else&lt;/code&gt; branch got another condition. The logic kept growing.&lt;/p&gt;

&lt;p&gt;That's when I realized: maybe counting "exactly in range" directly is the wrong approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if you only had one boundary?
&lt;/h2&gt;

&lt;p&gt;Consider a simpler question: how many subarrays have their maximum ≤ some value &lt;code&gt;k&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;This is dramatically easier. There's only one type of violation. If an element is ≤ &lt;code&gt;k&lt;/code&gt;, it extends your current valid window. If it's &amp;gt; &lt;code&gt;k&lt;/code&gt;, the window breaks. Two states, one condition.&lt;/p&gt;

&lt;p&gt;And here's the counting trick that makes it fast. At each position &lt;code&gt;i&lt;/code&gt;, the number of valid subarrays &lt;em&gt;ending at that position&lt;/em&gt; equals the current window length. If you've had 4 consecutive elements all ≤ &lt;code&gt;k&lt;/code&gt;, there are exactly 4 subarrays ending at position 3: the single element, the pair ending here, the triple, and all four together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;count_at_most&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire helper. Walk through &lt;code&gt;nums = [2, 1, 1, 3]&lt;/code&gt; with &lt;code&gt;k = 3&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Position&lt;/th&gt;
&lt;th&gt;Element&lt;/th&gt;
&lt;th&gt;≤ 3?&lt;/th&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Subarrays ending here&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[2]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[1], [2,1]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[1], [1,1], [2,1,1]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[3], [1,3], [1,1,3], [2,1,1,3]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Total: 1 + 2 + 3 + 4 = 10. Every subarray in the entire array has max ≤ 3.&lt;/p&gt;

&lt;h2&gt;
  
  
  The subtraction
&lt;/h2&gt;

&lt;p&gt;Now the key move. "Max is in &lt;code&gt;[left, right]&lt;/code&gt;" means "max ≤ right" AND "max ≥ left." That second condition is the annoying one. But you can rewrite it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;subarrays where max is in [left, right] = subarrays where max ≤ right &lt;strong&gt;minus&lt;/strong&gt; subarrays where max ≤ left - 1&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first count includes everything up to &lt;code&gt;right&lt;/code&gt;. The second count captures the ones that are too small (max below &lt;code&gt;left&lt;/code&gt;). Subtracting removes exactly those.&lt;/p&gt;

&lt;p&gt;Same array, &lt;code&gt;left = 2, right = 3&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;atMost(3)&lt;/code&gt; = 10 (computed above)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;atMost(1)&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Position&lt;/th&gt;
&lt;th&gt;Element&lt;/th&gt;
&lt;th&gt;≤ 1?&lt;/th&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Total: 3.&lt;/p&gt;

&lt;p&gt;Answer: 10 - 3 = &lt;strong&gt;7&lt;/strong&gt;. The valid subarrays are &lt;code&gt;[2], [2,1], [2,1,1], [2,1,1,3], [1,1,3], [1,3], [3]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The full solution is five lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Solution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;numSubarrayBoundedMax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;count_at_most&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
                &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;count_at_most&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;count_at_most&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where else this works (and where it doesn't)
&lt;/h2&gt;

&lt;p&gt;This decomposition isn't specific to "bounded max." It works anywhere you need to count subarrays defined by a range, as long as the "at most" version behaves nicely with a sliding window.&lt;/p&gt;

&lt;p&gt;LeetCode 992 (Subarrays with Exactly K Distinct Integers) is the same idea wearing different clothes. Counting "exactly K distinct" directly is painful because valid subarrays form a band in the middle of your window. Some shorter subarrays ending at the same position might have fewer than K distinct elements. Some longer ones might have more. You'd need two left pointers to track both edges of that band.&lt;/p&gt;

&lt;p&gt;But "at most K distinct" is a clean sliding window. One left pointer, shrink when you exceed K. So: &lt;code&gt;exactly(K) = atMost(K) - atMost(K - 1)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;LeetCode 1248 (Count Number of Nice Subarrays) is the same pattern again, just counting odd numbers instead of distinct elements.&lt;/p&gt;

&lt;p&gt;The pattern works because of a property called monotonicity: if a window satisfies "max ≤ k," every sub-window inside it also satisfies "max ≤ k." Same for "distinct count ≤ k." Shrinking the window from the left can never violate the property if it already holds.&lt;/p&gt;

&lt;p&gt;This breaks for something like "median ≤ k." Removing an element from the left of a window can increase or decrease the median unpredictably. You can't maintain a valid window by just shrinking, so the sliding window approach falls apart, and the subtraction decomposition with it.&lt;/p&gt;

&lt;p&gt;The general rule: if a range constraint is making your counting hard, check whether you can split it into two one-sided constraints. If each one-sided version has the monotonicity property (valid window implies all sub-windows are valid), the decomposition works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice problems
&lt;/h2&gt;

&lt;p&gt;In order of difficulty, all using this pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;LeetCode 713 (Subarray Product Less Than K)&lt;/strong&gt;: Single-sided, products instead of max. The "subarrays ending here = window size" insight is the same.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LeetCode 2110 (Number of Smooth Descent Periods)&lt;/strong&gt;: Run-length counting, almost identical to the &lt;code&gt;count_at_most&lt;/code&gt; helper.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LeetCode 992 (Subarrays with Exactly K Distinct Integers)&lt;/strong&gt;: The classic atMost(K) - atMost(K-1). Needs a hashmap for frequencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LeetCode 1248 (Count Number of Nice Subarrays)&lt;/strong&gt;: Same decomposition, counting odd numbers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LeetCode 2444 (Count Subarrays With Fixed Bounds)&lt;/strong&gt;: Harder. Two simultaneous constraints without the subtraction shortcut. Tests whether you can extend the thinking.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>algorithms</category>
      <category>leetcode</category>
      <category>dsa</category>
    </item>
    <item>
      <title>I Couldn't Count Subarrays Until I Thought About Handshakes</title>
      <dc:creator>Avinash Tyagi</dc:creator>
      <pubDate>Sat, 25 Apr 2026 10:04:37 +0000</pubDate>
      <link>https://forem.com/avinash_tyagi_98/i-couldnt-count-subarrays-until-i-thought-about-handshakes-2ee</link>
      <guid>https://forem.com/avinash_tyagi_98/i-couldnt-count-subarrays-until-i-thought-about-handshakes-2ee</guid>
      <description>&lt;p&gt;Hi everyone, I'm starting a series where I break down the core intuition behind coding patterns I've struggled to truly understand or reuse confidently. Not just the "how" but the "why does this actually work" part. Starting with one that tripped me up longer than I'd like to admit.&lt;/p&gt;

&lt;h2&gt;
  
  
  I Couldn't Count Subarrays Until I Thought About Handshakes
&lt;/h2&gt;

&lt;p&gt;I understood the &lt;em&gt;concept&lt;/em&gt; behind "Subarrays Divisible by K" for a while. Prefix sums, mod k, check for matching remainders. Cool. But every time I tried to write the actual counting logic, I'd freeze. Why does &lt;code&gt;count += map[remainder]&lt;/code&gt; work? Why not &lt;code&gt;count += 1&lt;/code&gt;? Where does the formula come from?&lt;/p&gt;

&lt;p&gt;It finally clicked when I stopped thinking about arrays and started thinking about people in a room.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap in my understanding
&lt;/h2&gt;

&lt;p&gt;Here's what I already knew: if two prefix sums have the same remainder when divided by k, the elements between them sum to a multiple of k. Standard prefix sum trick.&lt;/p&gt;

&lt;p&gt;What I &lt;em&gt;didn't&lt;/em&gt; get was the leap from "find matching remainders" to "count all valid subarrays." I could identify &lt;em&gt;that&lt;/em&gt; a valid subarray existed, but not how to count &lt;em&gt;all&lt;/em&gt; of them efficiently.&lt;/p&gt;

&lt;p&gt;Turns out I was missing some pretty basic math.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 1: The Handshake Problem
&lt;/h2&gt;

&lt;p&gt;5 people in a room. Everyone shakes hands with everyone else exactly once. How many handshakes?&lt;/p&gt;

&lt;p&gt;The formula is &lt;code&gt;n * (n-1) / 2&lt;/code&gt;. For 5 people, that's 10. But the formula alone wasn't what helped me. What helped was seeing it built &lt;strong&gt;incrementally&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Person A enters. Nobody there. &lt;strong&gt;0 handshakes.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Person B enters. Shakes hands with A. &lt;strong&gt;+1.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Person C enters. Shakes hands with A and B. &lt;strong&gt;+2.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Person D enters. Shakes with A, B, C. &lt;strong&gt;+3.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Person E enters. Shakes with A, B, C, D. &lt;strong&gt;+4.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total: 0 + 1 + 2 + 3 + 4 = &lt;strong&gt;10.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the same as 5×4/2 = 10. The incremental version and the formula give the same answer, just computed differently.&lt;/p&gt;

&lt;p&gt;This is exactly what the hashmap does. When you write &lt;code&gt;count += map[remainder]&lt;/code&gt;, you're saying: "this new prefix sum just walked into the room. How many prefix sums with the same remainder are already here? Shake hands with all of them."&lt;/p&gt;

&lt;p&gt;You don't add 1. You add however many are already waiting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 2: Modular Arithmetic (Remainder Buckets)
&lt;/h2&gt;

&lt;p&gt;When you divide any integer by k, the remainder is always between 0 and k-1. So every number falls into one of k "buckets."&lt;/p&gt;

&lt;p&gt;The rule that matters: &lt;strong&gt;if two numbers have the same remainder mod k, their difference is always divisible by k.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;14 % 5 = 4
 9 % 5 = 4
14 - 9 = 5   → divisible by 5 ✓
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works every time, not just with convenient examples. If &lt;code&gt;a % k == b % k&lt;/code&gt;, then &lt;code&gt;(a - b) % k == 0&lt;/code&gt;. Always.&lt;/p&gt;

&lt;p&gt;This is why we care about prefix sum remainders. The subarray sum between index i and j is &lt;code&gt;prefix[j] - prefix[i]&lt;/code&gt;. If both prefix sums land in the same remainder bucket, their difference (the subarray sum) is divisible by k.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It Together
&lt;/h2&gt;

&lt;p&gt;So the algorithm becomes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Compute prefix sums and their remainders (that's the mod arithmetic)&lt;/li&gt;
&lt;li&gt;Group them into remainder buckets&lt;/li&gt;
&lt;li&gt;Count pairs within each bucket (that's the handshake problem)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The hashmap approach does step 3 incrementally. Here's a concrete walkthrough with &lt;code&gt;arr = [4, 5, 0, -2, -3, 1]&lt;/code&gt;, &lt;code&gt;k = 5&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Start: map = {0: 1}   (prefix[-1] = 0, remainder 0)

i=0: prefix=4,  rem=4, map[4]=0  → count += 0, map={0:1, 4:1}
i=1: prefix=9,  rem=4, map[4]=1  → count += 1, map={0:1, 4:2}
i=2: prefix=9,  rem=4, map[4]=2  → count += 2, map={0:1, 4:3}
i=3: prefix=7,  rem=2, map[2]=0  → count += 0, map={0:1, 4:3, 2:1}
i=4: prefix=4,  rem=4, map[4]=3  → count += 3, map={0:1, 4:4, 2:1}
i=5: prefix=5,  rem=0, map[0]=1  → count += 1, map={0:2, 4:4, 2:1}

Total: 0 + 1 + 2 + 0 + 3 + 1 = 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at remainder 4. It appeared 4 times total (at prefix indices 0, 1, 2, 4). The incremental additions for that bucket alone were 0, 1, 2, 3 which sums to 6. And 4×3/2 = 6. Same answer. The handshake formula, built up one step at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The {0: 1} Seed
&lt;/h2&gt;

&lt;p&gt;One thing that confused me initially: why do we start the map with &lt;code&gt;{0: 1}&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Because there's a "virtual" prefix sum before index 0, which equals 0. If any prefix sum itself is divisible by k (remainder 0), it needs something to pair with. That virtual prefix[-1] = 0 is the pairing partner. Without it, you'd miss all subarrays that start from index 0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extending to "Subarray Sum Equals K" (LeetCode 560)
&lt;/h2&gt;

&lt;p&gt;Once you get the divisible-by-k version, this one is a small twist. Instead of "same remainder," you look for a specific difference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Divisible by K: prefix[j] % k == prefix[i] % k  →  look up same remainder
Sum Equals K:   prefix[j] - prefix[i] == k      →  look up (prefix[j] - k)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subarraySum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;freq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;freq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;freq&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;freq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same three ingredients: prefix sums give you the "what to track," the lookup condition tells you "what to search for," and the frequency map handles "how to count" (handshakes, not just existence).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; use a frequency dict, not a set. I made this mistake. A set only tells you "yes, it exists." A dict tells you "it exists 3 times," which means 3 valid subarrays, not 1. That's the handshake lesson all over again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Math Foundation I Was Missing
&lt;/h2&gt;

&lt;p&gt;If you're stuck on these problems like I was, here's the learning order that worked for me:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pair counting (n choose 2):&lt;/strong&gt; Get comfortable with why "n people, count handshakes" = n(n-1)/2, and why building it as 0+1+2+...+(n-1) gives the same answer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Modular arithmetic:&lt;/strong&gt; Drill the rule "same remainder means difference is divisible by k" until it's automatic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Connecting the two:&lt;/strong&gt; Subarray problems ask you to count pairs of prefix sums that satisfy some condition. Once you see that, the hashmap is just the efficient way to count those pairs on the fly.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Problems to Practice
&lt;/h2&gt;

&lt;p&gt;These all use the same pattern, roughly in order of difficulty:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LeetCode 560&lt;/strong&gt; - Subarray Sum Equals K (the classic)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LeetCode 974&lt;/strong&gt; - Subarrays Sums Divisible by K&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LeetCode 525&lt;/strong&gt; - Contiguous Array (convert 0s to -1s, then it's sum equals 0)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LeetCode 1248&lt;/strong&gt; - Count Number of Nice Subarrays&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LeetCode 1010&lt;/strong&gt; - Pairs of Songs Divisible by 60 (pure remainder + pairs, no prefix)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LeetCode 437&lt;/strong&gt; - Path Sum III (same technique, but on a tree)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The underlying skill isn't memorizing the prefix sum trick. It's recognizing when a subarray condition can be rewritten as a pairwise condition on prefix values, and then counting those pairs with a hashmap.&lt;/p&gt;

&lt;p&gt;That recognition is what these problems are really testing.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>coding</category>
      <category>100daysofcode</category>
    </item>
  </channel>
</rss>
