📘 LeetCode 1: Two Sum — Engineering & Algorithmic Solution Note
📌 Connected Resource Graph
- 🟢 Primary Theory Note: 06_hashing_cho_pair_sum_2sum_variations.md
- 🟡 Topic Overview Node: 00_overview_hash_tables_dictionary_hashmap.md
- 🎓 Vietnamese DSA Roadmap: 01_DSA/01_theory_and_patterns/Lộ trình học DSA - Nền tảng thuật toán.md
- 📅 Daily Study Tracker Note: day_01_arrays__hash_map_two_sum_easy.md
- 🗺️ Master Plan Roadmap: 28-Day DSA Master Plan
- 🍰 Interview Cake Curriculum: Week 04 PDF (Inflight Entertainment 2Sum Pattern)
- 📂 Local Project Companion Files:
- 💻
Program.cs(Runnable test suite with 7 test cases) - ⚡
complexity.md(Time & Space complexity deep dive) - 🚨
tips.md(Edge cases, pitfalls, & checklist) - ✍️
practice.md(Self-practice scratchpad & notes) - 📁
Answers/(Sub-approaches breakdown)
- 💻
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)
- Key: Element value (
- As we iterate through each element
nums[i], we calculatecomplement = target - nums[i]and query the hash map. - If
complementexists 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
| Approach | Time Complexity | Space Complexity | Best Use Case / Trade-offs |
|---|---|---|---|
| Approach 1: Brute Force | Memory-constrained systems where auxiliary memory is forbidden (). | ||
| Approach 2: Two-Pass Hash Map | Educational 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 Pointers | Optimal 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:
- Loop Iterations: The
forloop executes at most times. - Dictionary Operations:
- In .NET,
Dictionary<TKey, TValue>computes hash codes viaEqualityComparer<TKey>.Default.GetHashCode(). - Bucketing uses modulo arithmetic over prime-sized capacity arrays.
- Lookup (
TryGetValue) and Insertion (seen[key] = val) both run in amortized .
- In .NET,
- Total Time:
- 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:
- Auxiliary Data Structure:
Dictionary<int, int>stores at most entries before finding the matching complement.
- 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).
- A .NET
- Total Space: auxiliary heap allocation.
6. 🚨 Pitfalls, Edge Cases & Interview Traps
| Pitfall / Edge Case | Why It Occurs | How 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 Issue | In 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 & Zeroes | Arithmetic 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
- What if the array is already sorted?
- Use Two Pointers (left at
0, right atN - 1). Adjust pointers based onnums[left] + nums[right] <=> target. Time: , Space: auxiliary. (See LeetCode 167).
- Use Two Pointers (left at
- 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).
- What if data arrives as a continuous stream?
- Maintain a frequency dictionary or
HashSetin memory; on each incoming number , immediately check if exists.
- Maintain a frequency dictionary or
💡 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
| Metric | Brute Force | Two-Pass Hash Map | One-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
forloop executes at most times. - Hash Table Lookup:
seen.TryGetValue(complement, out int idx)hashes the integer key viaint.GetHashCode()(identity functionx => x).- Lookup in .NET
Dictionary<int, int>operates on average in time.
- Hash Table Insertion:
seen[nums[i]] = iassigns/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 eachEntrystruct 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
-
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).
-
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] != ito 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 = 6andnums = [3, 2, 4], checkingnums[0] + nums[0] = 6if 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.
- In a single-pass hash map, look up the complement before adding
Trap 2: Duplicate Numbers in Input
- Scenario:
nums = [3, 3],target = 6. - Mistake:
Dictionary.Add(key, value)throwsArgumentException: An item with the same key has already been addedif duplicate keys are inserted with.Add(). - Solution:
- Use
dict[nums[i]] = i(indexer) ordict.TryAdd(nums[i], i). - More importantly, for
[3, 3], when the loop arrives at index (the second3),complement = 6 - 3 = 3is already present at index in the map. The match returns[0, 1]immediately, before any duplicate insertion happens!
- Use
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
| Category | Input Array | Target | Expected Output | Verification 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
numsarray andtarget. - Return indices
[i, j]such thatnums[i] + nums[j] == targetandi != j. - Exactly one valid answer exists.
Thought Progression
-
Initial Instinct (Brute Force):
- For every element from to :
- For every element from to :
- If .
- For every element from to :
- Cost: time, space.
- For , operations, which may TLE or run near the time limit.
- For every element from to :
-
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:
- Stage 1: Traverse the array to populate the hash map with each element’s value and index.
- 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 != ito avoid self-pairing; duplicate elements overwrite their previous index in Pass 1.