<?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>DEV Community: Xinyang Wu</title>
    <description>The latest articles on DEV Community by Xinyang Wu (@xinyangwuethz).</description>
    <link>https://dev.to/xinyangwuethz</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%2F3971050%2F40c7cbce-7957-4642-a778-9b8f0329c053.png</url>
      <title>DEV Community: Xinyang Wu</title>
      <link>https://dev.to/xinyangwuethz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/xinyangwuethz"/>
    <language>en</language>
    <item>
      <title>6 Cross-Topic Lessons from Grinding 46 LeetCode Problems</title>
      <dc:creator>Xinyang Wu</dc:creator>
      <pubDate>Sat, 06 Jun 2026 10:02:16 +0000</pubDate>
      <link>https://dev.to/xinyangwuethz/6-cross-topic-lessons-from-grinding-46-leetcode-problems-1bh2</link>
      <guid>https://dev.to/xinyangwuethz/6-cross-topic-lessons-from-grinding-46-leetcode-problems-1bh2</guid>
      <description>&lt;p&gt;I'm 46 problems into a 12-week DSA sprint, keeping detailed notes on every bug I actually wrote — not the bugs that &lt;em&gt;could&lt;/em&gt; happen, but the ones that &lt;em&gt;did&lt;/em&gt;. Six lessons keep reappearing across completely different topics. Here they are, each with the real mistake and the counterexample that exposed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The sentinel is the identity element of your operation — and it goes in &lt;em&gt;front&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;Prefix sum arrays use a leading &lt;code&gt;prefix[0] = 0&lt;/code&gt;. Prefix &lt;em&gt;product&lt;/em&gt; arrays (LeetCode 238) use a leading &lt;code&gt;1&lt;/code&gt;. The hash map in subarray-sum problems (560, 523) seeds with the "empty prefix" entry. These aren't three tricks — they're one trick:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Prefix sum&lt;/th&gt;
&lt;th&gt;Prefix product&lt;/th&gt;
&lt;th&gt;Prefix + hash map&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sentinel&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;0&lt;/code&gt; (additive identity)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1&lt;/code&gt; (multiplicative identity)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;{0: 1}&lt;/code&gt; or &lt;code&gt;{0: -1}&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meaning&lt;/td&gt;
&lt;td&gt;"sum of an empty range is 0"&lt;/td&gt;
&lt;td&gt;"product of an empty range is 1"&lt;/td&gt;
&lt;td&gt;"an empty prefix exists"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kills which special case&lt;/td&gt;
&lt;td&gt;&lt;code&gt;left == 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"no left/right neighbor"&lt;/td&gt;
&lt;td&gt;"subarray starting at index 0"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The bug I actually wrote:&lt;/strong&gt; an &lt;em&gt;inclusive&lt;/em&gt; prefix sum with the sentinel &lt;code&gt;append&lt;/code&gt;ed to the &lt;em&gt;end&lt;/em&gt;, reached via Python's negative indexing:&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;sumRange&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;left&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="k"&gt;return&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;sum_val&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum_val&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;span class="c1"&gt;# left=0 → [-1] → wraps to the appended 0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It passed every test. It's still wrong — a "backdoor sentinel." Any reader sees &lt;code&gt;left - 1&lt;/code&gt; and assumes an off-by-one bug; in C++ or Java it &lt;em&gt;is&lt;/em&gt; one. Code that requires extra reasoning just to see why it doesn't crash will get flagged in any review. Put the identity element in front, where the index arithmetic reads naturally: &lt;code&gt;prefix[r+1] - prefix[l]&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Query &lt;em&gt;before&lt;/em&gt; insert
&lt;/h2&gt;

&lt;p&gt;The one-pass hash map pattern (Two Sum, 560) has a strict order: ask the map about your complement &lt;em&gt;first&lt;/em&gt;, then insert yourself.&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;for&lt;/span&gt; &lt;span class="n"&gt;n&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;bsval&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;beforesum&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;bsval&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="c1"&gt;# query history first
&lt;/span&gt;    &lt;span class="n"&gt;beforesum&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;bsval&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="c1"&gt;# then join history
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap those two lines and you match against &lt;em&gt;yourself&lt;/em&gt;. For 560 with &lt;code&gt;k = 0&lt;/code&gt; the counterexample is tiny: &lt;code&gt;nums = [1, -1]&lt;/code&gt; — correct answer 1, insert-before-query answer 4.&lt;/p&gt;

&lt;p&gt;The deeper mental model: &lt;strong&gt;a hash map in a scan is a record of the past&lt;/strong&gt;. "Look at the current element, ask about everything before it." Insert-first corrupts the definition of "before."&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The requirements &lt;em&gt;are&lt;/em&gt; the algorithm hint
&lt;/h2&gt;

&lt;p&gt;Three times now, a constraint I initially read as flavor text was actually the problem telling me which technique to use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"O(log n) required"&lt;/strong&gt; (LC 34): my first version found the target with binary search, then expanded linearly to find the range boundaries. On &lt;code&gt;[8,8,8,8,8]&lt;/code&gt; that expansion is O(n). The requirement existed precisely to exclude my approach. Correct answer: two independent lower-bound searches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"You may not use division"&lt;/strong&gt; (LC 238): not arbitrary cruelty — the obvious &lt;code&gt;total / nums[i]&lt;/code&gt; breaks the moment the array contains a zero. The constraint &lt;em&gt;is&lt;/em&gt; the hint: prefix × suffix products.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Array may contain negatives&lt;/strong&gt; (LC 560): silently kills sliding window, because window sums lose monotonicity. Only prefix sum + hash map survives.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;New rule: before coding, re-read the constraints and ask &lt;em&gt;"which obvious approach is this constraint designed to kill?"&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Don't mutate your input, and don't copy it either
&lt;/h2&gt;

&lt;p&gt;Two opposite sins, same root — not respecting the data you were handed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mutation&lt;/strong&gt; (LC 162, peak finding): I appended a &lt;code&gt;-inf&lt;/code&gt; sentinel to the input list. Two problems: it's a visible side effect (call the function twice, the list grows twice), and — funnier — tracing the index math showed my sentinel was &lt;em&gt;never read&lt;/em&gt;. The &lt;code&gt;-∞&lt;/code&gt; boundary in that problem is a reasoning tool, not a data structure you build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Copying&lt;/strong&gt; (LC 704, binary search): recursive binary search with slices:&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;return&lt;/span&gt; &lt;span class="nf"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mid&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="c1"&gt;# slices copy: T(n) = T(n/2) + O(n) = O(n)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Logically correct, and the logarithm is gone. The soul of binary search is &lt;em&gt;moving indices over unchanged data&lt;/em&gt;. Slicing quietly degrades O(log n) to O(n) — the worst kind of wrong, because every test still passes.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Two safety nets over one hole is worse than one
&lt;/h2&gt;

&lt;p&gt;My rotated-array-minimum (LC 153) solution passed, but contained &lt;em&gt;two&lt;/em&gt; mechanisms catching the same edge case: a "peek at the neighbor" early return, &lt;strong&gt;and&lt;/strong&gt; a final fallback — because my main loop discarded &lt;code&gt;mid&lt;/code&gt; even when &lt;code&gt;mid&lt;/code&gt; could be the answer.&lt;/p&gt;

&lt;p&gt;Delete either mechanism and it breaks. Keep both and it works — but now the code lies about its own invariant, and the next person to "simplify" it ships a bug.&lt;/p&gt;

&lt;p&gt;The honest version needs one rule: &lt;strong&gt;never discard an index that might be the answer.&lt;/strong&gt;&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;while&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;if&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;mid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;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;r&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;   &lt;span class="c1"&gt;# min is strictly right of mid
&lt;/span&gt;        &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&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;# mid can't be the answer → safe to discard
&lt;/span&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt;               &lt;span class="c1"&gt;# mid might be the answer → keep it
&lt;/span&gt;&lt;span class="k"&gt;return&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;l&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One side moves &lt;code&gt;mid ± 1&lt;/code&gt; (provably not the answer), the other stops &lt;em&gt;at&lt;/em&gt; &lt;code&gt;mid&lt;/code&gt; (possibly the answer). Every binary search variant I've written since reduces to deciding which side gets which.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. 90% of the bugs live in exactly two places
&lt;/h2&gt;

&lt;p&gt;After enough prefix-sum + hash problems, my bug reports converged: the algorithm is never the problem. The bugs are always in&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;the sentinel's index&lt;/strong&gt;, and&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;the problem's side constraint.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;LC 523 (subarray sum divisible by k, length ≥ 2) — I hit both in one submission:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Seeded the remainder map with &lt;code&gt;{0: 0}&lt;/code&gt; instead of &lt;code&gt;{0: -1}&lt;/code&gt;. The empty prefix lives at &lt;em&gt;virtual index -1&lt;/em&gt;. Counterexample: &lt;code&gt;nums = [3,3], k = 6&lt;/code&gt; — with &lt;code&gt;-1&lt;/code&gt; the length check gives &lt;code&gt;1 - (-1) = 2&lt;/code&gt; ✓; with &lt;code&gt;0&lt;/code&gt; it gives &lt;code&gt;1&lt;/code&gt;, and the answer is wrongly False.&lt;/li&gt;
&lt;li&gt;Returned True on any repeated remainder, forgetting &lt;strong&gt;length ≥ 2&lt;/strong&gt;. Counterexample: &lt;code&gt;nums = [5,1], k = 5&lt;/code&gt; — the very first element matches the sentinel and returns True for a length-1 subarray.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The template took five minutes. Finding those two counterexamples took the session. Now I write the boundary test cases &lt;em&gt;before&lt;/em&gt; the loop body: empty-prefix case, starts-at-zero case, minimum-length case.&lt;/p&gt;




&lt;h2&gt;
  
  
  The meta-lesson
&lt;/h2&gt;

&lt;p&gt;None of these are about knowing more algorithms. They're about &lt;em&gt;the same three centimeters around the algorithm&lt;/em&gt;: what the identity element is, what order state updates happen in, what the constraints quietly forbid, and which index is allowed to be discarded. That's where my actual bugs lived — all of them.&lt;/p&gt;

&lt;p&gt;I keep these notes per-topic with a "pitfalls I personally hit" section for every problem. Writing the counterexample down (not just the fix) is what made the patterns visible.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What cross-topic patterns have you noticed in your own grinding? I'd genuinely like to compare notes.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>leetcode</category>
      <category>python</category>
      <category>interview</category>
    </item>
  </channel>
</rss>
