📘 LeetCode 1: Two Sum — Engineering & Algorithmic Solution Note

📌 Connected Resource Graph


1. 🎯 Problem Statement & Constraints

Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.

You may assume that each input would have exactly one solution, and you may not use the same element twice. You can return the answer in any order.

Constraints

  • $2 \le \text{nums.length} \le 10
  • Only one valid answer exists.

2. 💡 Core Architectural Pattern: Hash Map Complement Lookup

Mathematical Invariant

The Algorithmic Paradigm Shift

  • A naive comparison scans all element pairs where . This requires operations.
  • Instead of repeatedly searching through the array for each candidate, we record previously encountered values inside a Hash Map / Hash Table where:
    • Key: Element value (nums[i])
    • Value: Element index (i)
  • As we iterate through each element nums[i], we calculate complement = target - nums[i] and query the hash map.
  • If complement exists in the hash map, the search terminates immediately in time.
  • If not, nums[i] is registered in the map for future elements to match against.
flowchart LR
    A["nums[i] at index i"] --> B["Compute complement = target - nums[i]"]
    B --> C{"dict.TryGetValue(complement, out idx)?"}
    C -- "YES" --> D["Match found: return [idx, i]"]
    C -- "NO" --> E["Insert dict[nums[i]] = i"]
    E --> F["Advance to i + 1"]

3. ⚡ Multi-Approach Comparison

ApproachTime ComplexitySpace ComplexityBest Use Case / Trade-offs
Approach 1: Brute ForceMemory-constrained systems where auxiliary memory is forbidden ().
Approach 2: Two-Pass Hash MapEducational baseline. Requires two traversals and explicit check dict[complement] != i.
Approach 3: One-Pass Hash Map (Optimal)Industry standard. Traverses array once, handles duplicates gracefully, prevents self-pairing naturally.
Approach 4: Sort + Two PointersOptimal when the array is already sorted (LeetCode 167: Two Sum II) or when only values are required.

4. 💻 Verified, Runnable C# Implementation

The following implementation is tested and verified in Program.cs.

using System;
using System.Collections.Generic;
 
public class Solution
{
    /// <summary>
    /// One-pass Hash Map solution for LeetCode 1 Two Sum.
    /// Time Complexity: O(N) average.
    /// Space Complexity: O(N) auxiliary space.
    /// </summary>
    public int[] TwoSum(int[] nums, int target)
    {
        // Allocate hash table: Key = value, Value = index
        var seen = new Dictionary<int, int>();
 
        for (int i = 0; i < nums.Length; i++)
        {
            int complement = target - nums[i];
 
            // Single lookup via TryGetValue (avoids double hashing of ContainsKey + indexer)
            if (seen.TryGetValue(complement, out int complementIndex))
            {
                return [complementIndex, i];
            }
 
            // Record current number's index for subsequent elements
            seen[nums[i]] = i;
        }
 
        return [];
    }
}

Demonstration & Verification

var sol = new Solution();
 
// Case 1: Standard pair
int[] r1 = sol.TwoSum([2, 7, 11, 15], 9);
Console.WriteLine($"[2, 7, 11, 15], target 9 => [{r1[0]}, {r1[1]}]"); // [0, 1]
 
// Case 2: Duplicate numbers forming target
int[] r2 = sol.TwoSum([3, 3], 6);
Console.WriteLine($"[3, 3], target 6 => [{r2[0]}, {r2[1]}]"); // [0, 1]
 
// Case 3: Negative numbers
int[] r3 = sol.TwoSum([-3, 4, 3, 90], 0);
Console.WriteLine($"[-3, 4, 3, 90], target 0 => [{r3[0]}, {r3[1]}]"); // [0, 2]

5. 📊 In-Depth Complexity Analysis

Time Complexity:

  1. Loop Iterations: The for loop executes at most times.
  2. Dictionary Operations:
    • In .NET, Dictionary<TKey, TValue> computes hash codes via EqualityComparer<TKey>.Default.GetHashCode().
    • Bucketing uses modulo arithmetic over prime-sized capacity arrays.
    • Lookup (TryGetValue) and Insertion (seen[key] = val) both run in amortized .
  3. Total Time:
  4. Catastrophic Worst-Case:
    • If an adversary induces collisions for all keys into a single hash bucket, search degrades to linear scan , giving total.
    • However, modern runtime defenses (randomized hash seeding in .NET) make this practically impossible in practice.

Space Complexity:

  1. Auxiliary Data Structure:
    • Dictionary<int, int> stores at most entries before finding the matching complement.
  2. Memory Layout in .NET:
    • A .NET Dictionary<int, int> allocates:
      • int[] _buckets: array of size (prime number ).
      • Entry[] _entries: array of structs containing { uint hashCode, int next, int key, int value } (24 bytes per entry on 64-bit systems).
  3. Total Space: auxiliary heap allocation.

6. 🚨 Pitfalls, Edge Cases & Interview Traps

Pitfall / Edge CaseWhy It OccursHow Our Solution Solves It
Self-Pairing Trap ()If dict.Add(nums[i], i) occurs before checking dict.ContainsKey(complement), when target = 2 * nums[i], the element matches itself.Checking TryGetValue before adding nums[i] prevents self-pairing.
Duplicate Values ([3, 3], target 6)Standard dictionary cannot contain duplicate keys.During the second occurrence, the first occurrence is already in the map and triggers early return before overwriting.
Two-Pass Overwrite IssueIn two-pass hash map, identical keys overwrite earlier indices (nums[0] = 3, nums[1] = 3 leaves only index 1).The one-pass algorithm returns on step 1 before any overwrite occurs.
Negative Targets & ZeroesArithmetic operations with negative numbers (-3 + 3 = 0).Two’s complement integer arithmetic handles negatives and zeroes naturally: target - (-3) = 0 + 3 = 3.

7. 🔗 Interview Follow-Up Questions

  1. What if the array is already sorted?
    • Use Two Pointers (left at 0, right at N - 1). Adjust pointers based on nums[left] + nums[right] <=> target. Time: , Space: auxiliary. (See LeetCode 167).
  2. What if we need all unique pairs that sum to target?
    • Sort the array first or track paired sets, and skip adjacent duplicates to avoid repeating pairs (See LeetCode 15: 3Sum).
  3. What if data arrives as a continuous stream?
    • Maintain a frequency dictionary or HashSet in memory; on each incoming number , immediately check if exists.

💡 Multi-Solution Analysis


title: “Complexity Deep Dive — LeetCode 1: Two Sum” tags: [leetcode-1, two-sum, complexity, big-o, hash-map, dsa, problem-solving, leetcode, learning-note]

⚡ Complexity Analysis — LeetCode 1: Two Sum

1. Asymptotic Complexity Summary

MetricBrute ForceTwo-Pass Hash MapOne-Pass Hash Map (Optimal)Two Pointers (with sort)
Best Time
Average Time
Worst Time
Space Complexity (index preservation)

*Worst-case time occurs in theoretical catastrophic hash collision scenarios where every key maps to a single bucket chain.


2. Deep Dive: One-Pass Hash Map

Time Complexity:

  • Array Traversal: A single for loop executes at most times.
  • Hash Table Lookup:
    • seen.TryGetValue(complement, out int idx) hashes the integer key via int.GetHashCode() (identity function x => x).
    • Lookup in .NET Dictionary<int, int> operates on average in time.
  • Hash Table Insertion:
    • seen[nums[i]] = i assigns/updates the dictionary entry in amortized time.
    • Periodic dynamic array resizing occurs when the load factor exceeds 1.0; resizing takes amortized across insertions.
  • Total Time:

Space Complexity:

  • Auxiliary Hash Map:
    • In the worst case (where the matching pair is the very last two elements or at indices and ), the dictionary will hold up to entries before the match is returned.
    • Thus, the space consumed is bounded by .
  • Heap Allocation Breakdown (.NET Core / .NET 9):
    • _buckets: int[M] array, where is a prime number greater than . Size: $4 \times M$ bytes.
    • _entries: Entry[M] array where each Entry struct is:
      struct Entry {
          uint hashCode; // 4 bytes
          int next;      // 4 bytes
          int key;       // 4 bytes
          int value;     // 4 bytes
      } // 16 bytes per entry (aligned to 24 bytes in 64-bit runtime)
    • For , total memory footprint is approximately $280 \text{ KB}$ — negligible for modern hardware.

3. Comparative Trade-offs

  1. Why not Sort + Two Pointers?

    • Sorting takes time.
    • Preserving original indices requires creating an array of tuples (num, originalIndex) which still takes auxiliary space.
    • Therefore, Sorting + Two Pointers is strictly inferior to Hash Map for LeetCode 1, though it is the optimal approach for LeetCode 167 (where the input array is already sorted and indices do not require extra storage).
  2. Why One-Pass instead of Two-Pass?

    • Two-pass iterates over the entire array twice ( operations vs operations).
    • Two-pass requires special logic to verify dict[complement] != i to avoid pairing an element with itself.
    • One-pass inherently solves self-pairing because an element is only inserted after its complement has been searched for.

🛠️ C# Tips & Common Pitfalls


title: “Interview Tips & Traps — LeetCode 1: Two Sum” tags: [leetcode-1, two-sum, interview-tips, edge-cases, hash-map, dsa, problem-solving, leetcode, learning-note]

🚨 Tips & Edge Cases — LeetCode 1: Two Sum

1. Top Traps & Gotchas

Trap 1: Reusing the Same Element ()

  • Mistake: When target = 6 and nums = [3, 2, 4], checking nums[0] + nums[0] = 6 if you search without checking indices.
  • Root Cause: An element at index cannot be added to itself.
  • Solution:
    • In a single-pass hash map, look up the complement before adding nums[i] to the map. The current element is not yet in the map, so it can never match with itself.

Trap 2: Duplicate Numbers in Input

  • Scenario: nums = [3, 3], target = 6.
  • Mistake: Dictionary.Add(key, value) throws ArgumentException: An item with the same key has already been added if duplicate keys are inserted with .Add().
  • Solution:
    • Use dict[nums[i]] = i (indexer) or dict.TryAdd(nums[i], i).
    • More importantly, for [3, 3], when the loop arrives at index (the second 3), complement = 6 - 3 = 3 is already present at index in the map. The match returns [0, 1] immediately, before any duplicate insertion happens!

Trap 3: Double Lookup Overhead in C#

  • Sub-optimal idiom:
    if (dict.ContainsKey(complement)) {
        return new int[] { dict[complement], i }; // Hashes key twice!
    }
  • Optimal idiom:
    if (dict.TryGetValue(complement, out int complementIndex)) {
        return [complementIndex, i]; // Hashes key only once!
    }

2. Exhaustive Test Suite Checklist

CategoryInput ArrayTargetExpected OutputVerification Notes
Standard Case[2, 7, 11, 15]9[0, 1]Complement found on second element.
Middle Elements[3, 2, 4]6[1, 2]Complement found between indices 1 and 2.
Duplicate Values[3, 3]6[0, 1]Target equals $2 \times \text{element}$.
Multiple Candidates[1, 4, 5, 6]10[1, 3]$4 + 6 = 10$, ignore 1 and 5.
Negative + Positive[-3, 4, 3, 90]0[0, 2]Signs cancel out naturally.
Zeroes[0, 4, 3, 0]0[0, 3]Complement $0 - 0 = 0$.
Negative Target[-1, -2, -3, -4, -5]-8[2, 4].

🏋️ Space to Practice


title: “Practice & Scratchpad — LeetCode 1: Two Sum” tags: [leetcode-1, two-sum, practice, hash-map, dsa, problem-solving, leetcode, learning-note]

✍️ Practice Runs & Scratchpad — LeetCode 1: Two Sum

1. Scratchpad & Conceptual Formulation

Problem Statement Recall

  • Given nums array and target.
  • Return indices [i, j] such that nums[i] + nums[j] == target and i != j.
  • Exactly one valid answer exists.

Thought Progression

  1. Initial Instinct (Brute Force):

    • For every element from to :
      • For every element from to :
        • If .
    • Cost: time, space.
    • For , operations, which may TLE or run near the time limit.
  2. Can we do better? Look up by value:

    • At each number , we need .
    • Has been seen before?
    • If we store in a Hash Map, lookup is !
    • Single pass: Check map for complement; if found, done; else save current number and continue.
    • Cost: time, space.

2. Pseudocode

function twoSum(nums, target):
    dict = empty hash map (value -> index)
 
    for i from 0 to length(nums) - 1:
        complement = target - nums[i]
        
        if complement exists in dict:
            return [dict[complement], i]
        
        dict[nums[i]] = i
    
    return []

3. Self-Practice Log

  • Date: 2026-09-03
  • Attempt 1: Brute force written in 2 minutes. Correct, verified .
  • Attempt 2: One-pass hash map implemented in C# 12. Correct handling of duplicates and self-pairing.
  • Verification: Ran dotnet run --project 08_projects_code/04_dsa_solutions/leetcode_solutions/1.two-sum — all 7 test cases passed cleanly.

🔬 Deep Dive: Sub-Approaches Analysis

Approach 1 Breakdown

1. Approach 1: Brute Force Nested Loops

Intuition

Check every possible pair of elements where $0 \le i < j < Nnums[i] + nums[j] == target[i, j]$.

C# Implementation

public class Solution
{
    public int[] TwoSum(int[] nums, int target)
    {
        int n = nums.Length;
        for (int i = 0; i < n - 1; i++)
        {
            for (int j = i + 1; j < n; j++)
            {
                if (nums[i] + nums[j] == target)
                {
                    return [i, j];
                }
            }
        }
        return [];
    }
}

Complexity Analysis

  • Time Complexity:
    • Total comparisons: . For , this is approximately $5 \times 10^7$ iterations.
  • Space Complexity:
    • Only loop index variables are stored on the stack.

Trade-offs

  • Pros: Zero auxiliary memory overhead; simple to understand and implement.
  • Cons: Quadratic runtime degrades severely as grows beyond .

Approach 2 Breakdown

2. Approach 2: Two-Pass Hash Map

Intuition

Break the algorithm into two distinct stages:

  1. Stage 1: Traverse the array to populate the hash map with each element’s value and index.
  2. Stage 2: Traverse the array again. For each element , calculate and check if it exists in the map. Ensure the found index is not itself.

C# Implementation

using System.Collections.Generic;
 
public class Solution
{
    public int[] TwoSum(int[] nums, int target)
    {
        var dict = new Dictionary<int, int>();
 
        // Pass 1: Add all elements to hash map
        for (int i = 0; i < nums.Length; i++)
        {
            dict[nums[i]] = i;
        }
 
        // Pass 2: Look up complement
        for (int i = 0; i < nums.Length; i++)
        {
            int complement = target - nums[i];
            if (dict.TryGetValue(complement, out int complementIndex) && complementIndex != i)
            {
                return [i, complementIndex];
            }
        }
 
        return [];
    }
}

Complexity Analysis

  • Time Complexity:
    • First loop executes times; second loop executes at most times.
    • operations .
  • Space Complexity:
    • Stores up to entries in the dictionary.

Trade-offs

  • Pros: Linear time complexity.
  • Cons: Requires two passes across the data; requires explicit check complementIndex != i to avoid self-pairing; duplicate elements overwrite their previous index in Pass 1.