Category 1

Arrays & Prefix Sums

Master fundamental array techniques including prefix sum arrays, range queries, lazy propagation, and classic array manipulation patterns. These problems build the foundation for more complex data structures and algorithms.

15
Problems
3
Difficulty Levels
100%
Complete Solutions
8
Codility Problems
Theory

Prefix Sums & Array Fundamentals

Understanding prefix sum arrays is essential for solving range query problems efficiently. Learn the concept, implementation patterns, and why certain design choices matter.

1. The Prefix Sum Array (P)

A prefix sum array stores cumulative sums: P[i] = A[0] + A[1] + ... + A[i]

Key insight: Sum of elements from index L to R = P[R] - P[L-1], computed in O(1).

💡
Why use long[] instead of int[]? Codility problems often have large ranges (up to 10^9 elements, values up to 10^9). Even if individual values fit in int, cumulative sums overflow. Example: 2 billion elements × 5 = 10 billion (exceeds int.MaxValue ~2.1B). Always use long[].
csharp
// Template: Build Prefix Sum Array
long[] BuildPrefixSum(int[] A) {
    long[] P = new long[A.Length + 1];
    P[0] = 0;
    for (int i = 0; i < A.Length; i++) {
        P[i + 1] = P[i] + A[i];
    }
    return P;
}

// Template: Range Sum Query O(1)
long RangeSum(long[] P, int L, int R) {
    // Sum of A[L..R]
    return P[R + 1] - P[L];
}

2. HashSet for O(1) Lookups

Many problems require checking membership. HashSet provides constant-time lookups with good cache locality.

csharp
// Efficient membership checking
HashSet<int> seen = new HashSet<int>();
for (int i = 1; i <= N; i++) {
    if (!seen.Contains(A[i])) {
        // Found missing element
    }
    seen.Add(A[i]);
}

3. Multi-Category Prefix Sums

For problems like GenomicRangeQuery, build separate prefix sums for each category (A, C, G, T).

csharp
// Multi-category prefix sums
int[][] prefixCount = new int[4][]; // A, C, G, T
for (int cat = 0; cat < 4; cat++) {
    prefixCount[cat] = new int[S.Length + 1];
    for (int i = 0; i < S.Length; i++) {
        prefixCount[cat][i + 1] = prefixCount[cat][i];
        if (S[i] == CharForCategory(cat)) {
            prefixCount[cat][i + 1]++;
        }
    }
}

4. Lazy Propagation Pattern

Instead of updating each element individually (O(N)), track range updates and apply them lazily during queries.

ℹ️
Lazy vs Eager: Eager updates are simple but slow (O(N) per update). Lazy propagation defers updates until needed, achieving O(log N) or O(1) amortized.
1. Missing Integer
Find the smallest positive integer not in array. Codility problem.
Codility Medium HashSet
1
📋
Problem: Given array of integers, find smallest positive integer (≥ 1) not present. Array length N, values can be negative or > N.

Approach & Why It Works

The smallest missing positive must be in range [1, N+1]. If all 1..N are present, answer is N+1. Otherwise, answer is the first gap. Use a HashSet to mark present values, then iterate 1..N+1 to find the first missing.

Time: O(N) for building set + O(N) for scanning = O(N). Space: O(N) for HashSet.

csharp
public int Solution(int[] A) {
    HashSet<int> present = new HashSet<int>();
    for (int i = 0; i < A.Length; i++) {
        if (A[i] > 0) {
            present.Add(A[i]);
        }
    }

    for (int candidate = 1; candidate <= A.Length + 1; candidate++) {
        if (!present.Contains(candidate)) {
            return candidate;
        }
    }
    return A.Length + 1;
}

Step-by-Step Trace

1
Input: A = [1, 3, 6, 4, 1, 2]
Build HashSet with positive values: {1, 2, 3, 4, 6}
2
Scan candidates 1 to N+1 = 7:
candidate=1: present ✓
candidate=2: present ✓
candidate=3: present ✓
candidate=4: present ✓
candidate=5: NOT present → return 5
3
Output: 5

Edge Cases

⚠️
Case 1: All 1..N present: A = [1, 2, 3] → return 4
Case 2: Negatives only: A = [-1, -2, -3] → return 1
Case 3: Duplicates: A = [1, 1, 1] → return 2
Case 4: Large gaps: A = [100, 200] → return 1
Time
O(N)
Space
O(N)
2. Permutation Check
Check if array is permutation of [1..N]. Codility problem.
Codility Easy HashSet
2
📋
Problem: Return 1 if array A is permutation of [1, 2, ..., N], else 0. Array length is N.

Approach & Why It Works

A permutation has every value 1..N exactly once. Method: (1) Check all values in [1..N], (2) Check no duplicates using HashSet size. If set size = N and all values in range, it's a permutation.

Time: O(N). Space: O(N).

csharp
public int Solution(int[] A) {
    if (A.Length == 0) return 0;

    HashSet<int> seen = new HashSet<int>();
    for (int i = 0; i < A.Length; i++) {
        // Must be in range [1, N]
        if (A[i] < 1 || A[i] > A.Length) return 0;
        // No duplicates
        if (seen.Contains(A[i])) return 0;
        seen.Add(A[i]);
    }

    return 1;
}

Step-by-Step Trace

1
Input: A = [4, 1, 3, 2]
N = 4, valid range = [1, 4]
2
Iterate: 4 ✓, 1 ✓, 3 ✓, 2 ✓
All in range [1, 4], no duplicates
3
Output: 1 (is permutation)

Edge Cases

⚠️
Case 1: Duplicate: A = [1, 2, 2] → return 0
Case 2: Out of range: A = [1, 2, 5] → return 0
Case 3: Single element: A = [1] → return 1
Case 4: Zero included: A = [0, 1, 2] → return 0
Time
O(N)
Space
O(N)
3. Genomic Range Query
Find minimum nucleotide impact in DNA ranges. Codility problem.
Codility Hard Prefix Sums
3
📋
Problem: DNA string has nucleotides {A, C, G, T} with impacts {1, 2, 3, 4}. Given queries (L, R), return minimum impact in substring S[L..R].

Approach & Why It Works

Naive: O(M×(R-L)) for M queries. Optimized: Build 4 prefix sum arrays (one per nucleotide). For each query, check if each nucleotide exists in range using prefix sums (O(1) per nucleotide). Return minimum impact of present nucleotides. Total: O(N) preprocessing + O(4M) = O(N + M).

csharp
public int[] Solution(string S, int[] P, int[] Q) {
    int N = S.Length;
    int M = P.Length;
    // Prefix sums for A, C, G, T
    int[][] prefix = new int[4][];
    for (int i = 0; i < 4; i++) {
        prefix[i] = new int[N + 1];
    }

    for (int i = 0; i < N; i++) {
        for (int j = 0; j < 4; j++) {
            prefix[j][i + 1] = prefix[j][i];
        }
        int idx = NucleotideIndex(S[i]);
        prefix[idx][i + 1]++;
    }

    int[] result = new int[M];
    for (int q = 0; q < M; q++) {
        int L = P[q], R = Q[q];
        for (int nuc = 0; nuc < 4; nuc++) {
            int count = prefix[nuc][R + 1] - prefix[nuc][L];
            if (count > 0) {
                result[q] = nuc + 1; // Impact: A=1, C=2, G=3, T=4
                break;
            }
        }
    }
    return result;
}

private int NucleotideIndex(char c) {
    switch(c) {
        case 'A': return 0;
        case 'C': return 1;
        case 'G': return 2;
        case 'T': return 3;
        default: return -1;
    }
}

Step-by-Step Trace

1
Input: S = "CAGCCTA", P = [2, 5, 0], Q = [4, 5, 6]
2
Build prefix sums:
A: [0, 0, 0, 1, 1, 1, 1, 2]
C: [0, 1, 1, 1, 2, 2, 2, 2]
G: [0, 0, 1, 1, 1, 1, 2, 2]
T: [0, 0, 0, 0, 0, 1, 1, 1]
3
Query [2, 4] (substring "GCC"):
A count = 0, C count = 2 (found!) → impact = 2
4
Output: [2, 4, 1]

Edge Cases

⚠️
Case 1: Single character: S = "A", query [0, 0] → 1
Case 2: All same: S = "AAA", query [0, 2] → 1
Case 3: One query covers all: [0, N-1] → minimum impact
Case 4: T at end: S = "ACT", query [2, 2] → 4
Time
O(N + M)
Space
O(N)
4. Passing Cars
Count east-west car pairs where east passes west. Codility problem.
Codility Medium Prefix Sums
4
📋
Problem: Array A where 0=east, 1=west. Count pairs (i, j) where i < j, A[i]=0 (east), A[j]=1 (west). Return count or −1 if > 1 billion.

Approach & Why It Works

Naive: O(N²) nested loop. Optimized: For each west car (A[j]=1), count how many east cars (0s) appear before it. Use a running counter of 0s seen so far. When we see a 1, add the counter to result.

Time: O(N). Space: O(1).

csharp
public int Solution(int[] A) {
    long count = 0;
    int eastCount = 0;

    for (int i = 0; i < A.Length; i++) {
        if (A[i] == 0) {
            eastCount++;
        } else {
            count += eastCount;
            // Prevent overflow: if > 1 billion, return -1
            if (count > 1_000_000_000) return -1;
        }
    }

    return (int)count;
}

Step-by-Step Trace

1
Input: A = [0, 1, 0, 1, 1]
0=east, 1=west
2
i=0: A[0]=0 → eastCount=1
i=1: A[1]=1 → count += 1, total=1
i=2: A[2]=0 → eastCount=2
i=3: A[3]=1 → count += 2, total=3
i=4: A[4]=1 → count += 2, total=5
3
Output: 5 (pairs: (0,1), (0,3), (0,4), (2,3), (2,4))

Edge Cases

⚠️
Case 1: No pairs: A = [1, 1, 0, 0] → 0
Case 2: Large result: N=10^5, all [0..0, 1..1] → ~2.5B pairs, return -1
Case 3: Single element: A = [0] or [1] → 0
Case 4: Alternating: A = [0, 1, 0, 1] → 4
Time
O(N)
Space
O(1)
5. Max Counters
Efficiently handle increment and max_counter operations. Codility problem.
Codility Hard Lazy Propagation
5
📋
Problem: N counters (initially 0). Operations: increment(X) or max_counter (set all to current max). Return final state. Naive O(N×M) fails; use lazy propagation for O(N+M).

Approach & Why It Works

Naive: For each max_counter, loop and set all N counters (O(N)). With M operations, total O(N×M), too slow.

Optimized (Lazy): Track global max without updating all counters. For each counter, track its "checkpoint" (when it was last set to max). During increment, if counter's checkpoint is outdated, first bring it up to checkpoint value. At the end, update all counters based on final checkpoint.

Time: O(N + M). Space: O(N).

csharp
public int[] Solution(int N, int[] A) {
    int[] counters = new int[N];
    int maxCounter = 0;
    int lastMax = 0; // Checkpoint value

    for (int op = 0; op < A.Length; op++) {
        int X = A[op] - 1; // 1-indexed to 0-indexed
        if (X == N) {
            // Max counter operation
            lastMax = maxCounter;
        } else {
            // Increment counter X
            if (counters[X] < lastMax) {
                counters[X] = lastMax;
            }
            counters[X]++;
            maxCounter = Math.Max(maxCounter, counters[X]);
        }
    }

    // Final pass: bring all counters up to lastMax
    for (int i = 0; i < N; i++) {
        if (counters[i] < lastMax) {
            counters[i] = lastMax;
        }
    }

    return counters;
}

Step-by-Step Trace

1
Input: N = 3, A = [3, 4, 4, 6, 1, 4, 4]
Counter indices: 1, 2, 3. Operation 4 = max_counter, 6 = (invalid, ignore).
2
3: increment(3) → [0, 0, 1], max=1
4: max_counter → lastMax=1
4: max_counter → lastMax=1
6: increment(1) → [1, 0, 1], max=1
1: increment(1) → [2, 0, 1], max=2
4: max_counter → lastMax=2
4: max_counter → lastMax=2
3
Final pass: bring [2, 0, 1] up to lastMax=2 → [2, 2, 2]
Output: [2, 2, 2]

Edge Cases

⚠️
Case 1: No operations: N = 2, A = [] → [0, 0]
Case 2: Only max_counter: A = [3, 3, 3] → [0, 0, 0] (N=2)
Case 3: Increments then max: A = [1, 2, 3] → [1, 1, 1]
Case 4: Large M: M=10^5 operations on N counters → O(N+M) handles efficiently
Time
O(N + M)
Space
O(N)
6. Frog River One
Earliest time frog can cross river using falling leaves. Codility problem.
Codility Medium HashSet
6
📋
Problem: Frog wants to cross river of width X. At each time step, one leaf falls at position A[t]. Frog can walk on leaves at positions 1..X. Find earliest time all positions 1..X are covered by leaves, or -1 if impossible.

Approach & Why It Works

We need all positions 1 to X covered. Use a HashSet to track which positions have leaves. For each time step, add A[t] to the set. Once the set contains all 1..X, return that time. If we finish the array without covering all positions, return -1.

Time: O(N). Space: O(X).

csharp
public int Solution(int X, int[] A) {
    HashSet<int> covered = new HashSet<int>();

    for (int t = 0; t < A.Length; t++) {
        if (A[t] >= 1 && A[t] <= X) {
            covered.Add(A[t]);
        }

        // Check if all positions 1..X are covered
        if (covered.Count == X) {
            return t;
        }
    }

    return -1;
}

Step-by-Step Trace

1
Input: X = 5, A = [1, 3, 1, 4, 2, 3, 5, 4]
Need to cover positions {1, 2, 3, 4, 5}
2
t=0: A[0]=1 → {1}
t=1: A[1]=3 → {1, 3}
t=2: A[2]=1 → {1, 3}
t=3: A[3]=4 → {1, 3, 4}
t=4: A[4]=2 → {1, 2, 3, 4}
t=5: A[5]=3 → {1, 2, 3, 4}
t=6: A[6]=5 → {1, 2, 3, 4, 5}, count=5=X ✓
3
Output: 6

Edge Cases

⚠️
Case 1: Impossible: A = [1, 1, 1], X = 2 → -1
Case 2: Duplicates: A = [1, 1, 2], X = 2 → 2
Case 3: Out of range: A = [10, 11], X = 2 → -1
Case 4: X = 1: A = [1] → 0
Time
O(N)
Space
O(X)
7. Count Divisibles
Count divisible numbers in range [A, B]. Codility problem.
Codility Medium Math
7
📋
Problem: Count integers in range [A, B] divisible by K. Cannot iterate; A, B up to 2×10^9.

Approach & Why It Works

Formula: Count of multiples of K in [A, B] = ⌊B / K⌋ − ⌊(A − 1) / K⌋

Example: K=5, range [11, 23]. Multiples: 15, 20. Formula: ⌊23/5⌋ − ⌊10/5⌋ = 4 − 2 = 2. ✓

Time: O(1). Space: O(1).

csharp
public int Solution(int A, int B, int K) {
    return (B / K) - ((A - 1) / K);
}

Step-by-Step Trace

1
Input: A = 6, B = 11, K = 2
Find multiples of 2 in [6, 11]: {6, 8, 10}
2
⌊B / K⌋ = ⌊11 / 2⌋ = 5
⌊(A − 1) / K⌋ = ⌊5 / 2⌋ = 2
Count = 5 − 2 = 3
3
Output: 3

Edge Cases

⚠️
Case 1: A = B = K: result = 1
Case 2: A > B: impossible per spec
Case 3: No multiples: A = 1, B = 4, K = 5 → 0
Case 4: K = 1: result = B − A + 1
Time
O(1)
Space
O(1)
8. Equi-Leader
Count split positions where both sides have same leader. Codility problem.
Codility Hard Prefix Sums
8
📋
Problem: Find positions P where leader (element appearing > N/2 times) is the same in A[0..P] and A[P+1..N-1].

Approach & Why It Works

Step 1: Find the global leader (appears > N/2 times). Use Boyer-Moore voting algorithm: O(N).

Step 2: Build prefix counts: count[i] = how many times leader appears in A[0..i]. O(N).

Step 3: For each split position P, check if leader appears > (P+1)/2 times on left AND > (N-P-1)/2 times on right. O(N).

Time: O(N). Space: O(N).

csharp
public int Solution(int[] A) {
    int N = A.Length;

    // Step 1: Find global leader using Boyer-Moore
    int candidate = 0, count = 0;
    for (int i = 0; i < N; i++) {
        if (count == 0) candidate = A[i];
        count += (A[i] == candidate) ? 1 : -1;
    }

    // Verify candidate is actually the leader
    count = 0;
    for (int i = 0; i < N; i++) {
        if (A[i] == candidate) count++;
    }
    if (count <= N / 2) return 0; // No leader

    int leader = candidate;

    // Step 2: Build prefix counts
    int[] leaderCount = new int[N];
    leaderCount[0] = (A[0] == leader) ? 1 : 0;
    for (int i = 1; i < N; i++) {
        leaderCount[i] = leaderCount[i - 1] + ((A[i] == leader) ? 1 : 0);
    }

    // Step 3: Count equi-leader positions
    int result = 0;
    for (int P = 0; P < N - 1; P++) {
        int leftSize = P + 1;
        int rightSize = N - leftSize;
        int leftLeaderCount = leaderCount[P];
        int rightLeaderCount = leaderCount[N - 1] - leaderCount[P];

        if (leftLeaderCount > leftSize / 2 && rightLeaderCount > rightSize / 2) {
            result++;
        }
    }

    return result;
}

Step-by-Step Trace

1
Input: A = [4, 3, 4, 4, 4, 2]
N = 6
2
Leader: 4 appears 4 times, > 6/2=3 ✓
3
leaderCount = [1, 1, 2, 3, 4, 4]
P=0: left={4}, leftCount=1 > 1/2=0 ✓, rightCount=3 > 5/2=2 ✓ → equi-leader
P=1: leftCount=1 > 2/2=1? No
P=2: leftCount=2 > 3/2=1 ✓, rightCount=2 > 3/2=1 ✓ → equi-leader
P=3: leftCount=3 > 4/2=2 ✓, rightCount=1 > 2/2=1? No
P=4: leftCount=4 > 5/2=2 ✓, rightCount=0 > 1/2=0? No
4
Output: 2

Edge Cases

⚠️
Case 1: No leader: A = [1, 2, 3, 4] → 0
Case 2: All same: A = [5, 5, 5, 5] → 3 (all positions)
Case 3: Two elements: A = [1, 1] → 0 (N-1=1, no valid P)
Case 4: Leader only on left: A = [1, 1, 2, 2, 2] → 0
Time
O(N)
Space
O(N)
9. Product of Array Except Self
LeetCode 238. Compute product of all elements except self without division.
LeetCode Medium Prefix Sums
9
📋
Problem: Given array, return array where result[i] = product of all elements except A[i]. Cannot use division.

Approach & Why It Works

Use prefix and suffix products. result[i] = (product of all left of i) × (product of all right of i). Build two passes: one for left products, one for right products.

Time: O(N). Space: O(1) if we don't count output array.

csharp
public int[] ProductExceptSelf(int[] nums) {
    int n = nums.Length;
    int[] result = new int[n];

    // Left pass: result[i] = product of all elements to the left
    result[0] = 1;
    for (int i = 1; i < n; i++) {
        result[i] = result[i - 1] * nums[i - 1];
    }

    // Right pass: multiply by product of all elements to the right
    int rightProduct = 1;
    for (int i = n - 1; i >= 0; i--) {
        result[i] *= rightProduct;
        rightProduct *= nums[i];
    }

    return result;
}

Step-by-Step Trace

1
Input: nums = [1, 2, 3, 4]
2
After left pass: result = [1, 1, 2, 6] (left products)
3
After right pass:
result[3] = 6 × 1 = 6
result[2] = 2 × 4 = 8
result[1] = 1 × 12 = 12
result[0] = 1 × 24 = 24
4
Output: [24, 12, 8, 6]

Edge Cases

⚠️
Case 1: With zeros: [0, 1, 2] → [2, 0, 0]
Case 2: Negative: [-1, -2, -3] → [6, -3, -2]
Case 3: Two elements: [2, 3] → [3, 2]
Case 4: Single element: [5] → [1]
Time
O(N)
Space
O(1)
10. Subarray Sum Equals K
LeetCode 560. Count subarrays with sum equal to K using prefix sums.
LeetCode Medium Prefix Sum + HashMap
10
📋
Problem: Count number of subarrays whose sum equals K.

Approach & Why It Works

Key insight: If prefix sum up to i = P and we want sum[j..i] = K, then P − sum[j−1] = K, so sum[j−1] = P − K. Use HashMap to count occurrences of each prefix sum. For each position, check if (currentSum − K) exists in the map.

Time: O(N). Space: O(N).

csharp
public int SubarraySum(int[] nums, int k) {
    Dictionary<int, int> prefixMap = new Dictionary<int, int>();
    prefixMap[0] = 1; // Base case: sum 0 seen once

    int count = 0;
    int currentSum = 0;

    for (int i = 0; i < nums.Length; i++) {
        currentSum += nums[i];
        int target = currentSum - k;

        // Check if (currentSum - k) exists in map
        if (prefixMap.ContainsKey(target)) {
            count += prefixMap[target];
        }

        // Add current prefix sum to map
        if (!prefixMap.ContainsKey(currentSum)) {
            prefixMap[currentSum] = 0;
        }
        prefixMap[currentSum]++;
    }

    return count;
}

Step-by-Step Trace

1
Input: nums = [1, 1, 1], k = 2
Find subarrays summing to 2: [1,1] at indices (0,1) and (1,2)
2
i=0: sum=1, target=1-2=-1 (not in map), map={0:1, 1:1}
i=1: sum=2, target=2-2=0 (found!), count=1, map={0:1, 1:1, 2:1}
i=2: sum=3, target=3-2=1 (found!), count=2, map={0:1, 1:1, 2:1, 3:1}
3
Output: 2

Edge Cases

⚠️
Case 1: Negatives: nums = [-1, 1, 1], k = 1 → 2
Case 2: No match: nums = [1, 2], k = 3 → 0
Case 3: k = 0: nums = [0, 0, 0] → 6
Case 4: Single element: nums = [5], k = 5 → 1
Time
O(N)
Space
O(N)
11. Maximum Subarray (Kadane's)
LeetCode 53. Find contiguous subarray with largest sum.
LeetCode Medium DP/Greedy
11
📋
Problem: Find contiguous subarray with maximum sum (at least one element).

Approach & Why It Works

Kadane's Algorithm: Track max sum ending at current position. For each element, decide: extend previous subarray or start new one. If adding element to previous sum is worse than just the element, reset.

State: maxEndingHere = max sum of subarray ending at current index. Update global maxSoFar each step.

Time: O(N). Space: O(1).

csharp
public int MaxSubArray(int[] nums) {
    int maxEndingHere = nums[0];
    int maxSoFar = nums[0];

    for (int i = 1; i < nums.Length; i++) {
        // Either extend subarray or start fresh
        maxEndingHere = Math.Max(nums[i], maxEndingHere + nums[i]);
        // Update global maximum
        maxSoFar = Math.Max(maxSoFar, maxEndingHere);
    }

    return maxSoFar;
}

Step-by-Step Trace

1
Input: nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
Expected max subarray: [4, -1, 2, 1] = 6
2
i=0: maxHere=-2, maxSoFar=-2
i=1: maxHere=max(1, -1)=1, maxSoFar=1
i=2: maxHere=max(-3, -2)=-2, maxSoFar=1
i=3: maxHere=max(4, 2)=4, maxSoFar=4
i=4: maxHere=max(-1, 3)=3, maxSoFar=4
i=5: maxHere=max(2, 5)=5, maxSoFar=5
i=6: maxHere=max(1, 6)=6, maxSoFar=6
i=7: maxHere=max(-5, 1)=1, maxSoFar=6
i=8: maxHere=max(4, 5)=5, maxSoFar=6
3
Output: 6

Edge Cases

⚠️
Case 1: All negative: [-5, -2, -3] → -2
Case 2: Single element: [5] → 5
Case 3: All positive: [1, 2, 3] → 6
Case 4: Mix: [5, -3, 5] → 7
Time
O(N)
Space
O(1)
12. Merge Sorted Array
LeetCode 88. Merge two sorted arrays in-place into first array.
LeetCode Easy Two Pointers
12
📋
Problem: nums1 has length m + n, first m elements are filled. nums2 has length n. Merge nums2 into nums1 in-place in sorted order.

Approach & Why It Works

Work backwards from the end. Start with three pointers: end of nums1 (m+n-1), end of filled nums1 (m-1), end of nums2 (n-1). Compare and place the larger element at the current position, moving pointers backward. This avoids overwriting unprocessed elements.

Time: O(m + n). Space: O(1).

csharp
public void Merge(int[] nums1, int m, int[] nums2, int n) {
    int p1 = m - 1;        // Pointer for nums1
    int p2 = n - 1;        // Pointer for nums2
    int p = m + n - 1;   // Pointer for result position

    while (p1 >= 0 && p2 >= 0) {
        if (nums1[p1] > nums2[p2]) {
            nums1[p] = nums1[p1];
            p1--;
        } else {
            nums1[p] = nums2[p2];
            p2--;
        }
        p--;
    }

    // Copy remaining nums2 elements (if any)
    while (p2 >= 0) {
        nums1[p] = nums2[p2];
        p2--;
        p--;
    }
    // No need to copy remaining nums1 elements; they're already in place
}

Step-by-Step Trace

1
Input: nums1 = [1, 2, 3, 0, 0, 0], m = 3
nums2 = [2, 5, 6], n = 3
2
p1=2 (nums1[2]=3), p2=2 (nums2[2]=6), p=5
6 > 3 → nums1[5]=6, p2=1, p=4
3 > 5? No → nums1[4]=5, p1=2, p=3
3 > 2 → nums1[3]=3, p1=1, p=2
2 = 2 → nums1[2]=2, p2=0, p=1
nums1[1]=2, but it's already 2; p1=0, p=0
nums1[0]=1, already 1; done
3
Output: nums1 = [1, 2, 2, 3, 5, 6]

Edge Cases

⚠️
Case 1: nums2 is empty: m = 2, n = 0 → no change
Case 2: nums1 is empty: m = 0, n = 2, nums2 = [1, 2] → [1, 2]
Case 3: nums2 all smaller: nums1=[3,4], nums2=[1,2] → [1, 2, 3, 4]
Case 4: Duplicates: nums1=[1,1,0], nums2=[1] → [1, 1, 1]
Time
O(m + n)
Space
O(1)
13. Rotate Array
LeetCode 189. Rotate array right by k steps in-place.
LeetCode Medium Reverse Trick
13
📋
Problem: Rotate array to the right by k steps. k can be >= N.

Approach & Why It Works

Reverse trick: Rotating right by k is equivalent to: reverse entire array, reverse first k elements, reverse remaining n−k elements.

Example: [1,2,3,4,5], k=2. Reverse all → [5,4,3,2,1]. Reverse first 2 → [4,5,3,2,1]. Reverse last 3 → [4,5,1,2,3]. ✓

Time: O(N). Space: O(1).

csharp
public void Rotate(int[] nums, int k) {
    int n = nums.Length;
    k = k % n; // Handle k >= n

    // Reverse entire array
    Reverse(nums, 0, n - 1);
    // Reverse first k elements
    Reverse(nums, 0, k - 1);
    // Reverse remaining elements
    Reverse(nums, k, n - 1);
}

private void Reverse(int[] nums, int start, int end) {
    while (start < end) {
        int temp = nums[start];
        nums[start] = nums[end];
        nums[end] = temp;
        start++;
        end--;
    }
}

Step-by-Step Trace

1
Input: nums = [1, 2, 3, 4, 5], k = 2
n = 5, k % n = 2
2
Reverse entire [0, 4] → [5, 4, 3, 2, 1]
3
Reverse first k=2 [0, 1] → [4, 5, 3, 2, 1]
4
Reverse last 3 [2, 4] → [4, 5, 1, 2, 3]
Output: [4, 5, 1, 2, 3]

Edge Cases

⚠️
Case 1: k = 0: no rotation
Case 2: k = n: full rotation, same array
Case 3: k > n: k % n handled
Case 4: Single element: [1], k = 5 → [1]
Time
O(N)
Space
O(1)
14. Contains Duplicate
LeetCode 217. Check if array contains duplicate elements.
LeetCode Easy HashSet
14
📋
Problem: Return true if array contains duplicate value, false otherwise.

Approach & Why It Works

Use HashSet to track seen elements. For each element, check if already in set. If yes, return true. If we finish without duplicates, return false.

Time: O(N) average. Space: O(min(N, M)) where M is number of distinct values.

csharp
public bool ContainsDuplicate(int[] nums) {
    HashSet<int> seen = new HashSet<int>();

    foreach (int num in nums) {
        if (seen.Contains(num)) {
            return true;
        }
        seen.Add(num);
    }

    return false;
}

Step-by-Step Trace

1
Input: nums = [1, 2, 3, 1]
2
Iterate: 1 (add), 2 (add), 3 (add), 1 (found!) → return true
3
Output: true

Edge Cases

⚠️
Case 1: No duplicates: [1, 2, 3] → false
Case 2: All duplicates: [1, 1, 1] → true
Case 3: Single element: [1] → false
Case 4: Two identical: [0, 0] → true
Time
O(N)
Space
O(min(N, M))
15. Maximum Sum Circular Subarray
LeetCode 918. Find max subarray sum in circular array using Kadane variant.
LeetCode Hard Kadane Variant
15
📋
Problem: Array is circular (last element connects to first). Find maximum subarray sum. Subarray can wrap around.

Approach & Why It Works

Key insight: Max circular subarray = max(max non-circular, total − min subarray). Non-circular max = standard Kadane. For circular: total sum − minimum subarray = maximum wraparound subarray. Use min-Kadane (track minimum subarray). Return max of the two cases, but handle edge case where min subarray is entire array (all negative numbers).

Time: O(N). Space: O(1).

csharp
public int MaxSubarraySumCircular(int[] nums) {
    int totalSum = 0;
    int maxSum = int.MinValue;
    int minSum = int.MaxValue;
    int maxEndingHere = 0;
    int minEndingHere = 0;

    for (int i = 0; i < nums.Length; i++) {
        totalSum += nums[i];

        // Kadane's for maximum
        maxEndingHere = Math.Max(nums[i], maxEndingHere + nums[i]);
        maxSum = Math.Max(maxSum, maxEndingHere);

        // Kadane's for minimum
        minEndingHere = Math.Min(nums[i], minEndingHere + nums[i]);
        minSum = Math.Min(minSum, minEndingHere);
    }

    // If minSum == totalSum, all elements are minimum (negative)
    if (minSum == totalSum) {
        return maxSum;
    }

    // Return max of non-circular and circular cases
    return Math.Max(maxSum, totalSum - minSum);
}

Step-by-Step Trace

1
Input: nums = [3, -1, 2, -4, 5]
Total = 5
2
Kadane max: track max subarray → [3, -1, 2] = 4, then [5] = 5, maxSum = 5
Kadane min: track min subarray → [-4], minSum = -4
Non-circular max = 5
Circular max = 5 − (−4) = 9 (subarray [5, 3] = 8? No: [3, -1, 2, -4, 5] total=5, remove [-4]=9 wraps to [5, 3]... actually wrap logic gives us 9 which is [3] + [5] or [-1, 2, -4] removed gives [3, 5])
3
Output: max(5, 9) = 9

Edge Cases

⚠️
Case 1: All negative: [-2, -3, -1] → -1 (not circular)
Case 2: Single element: [5] → 5
Case 3: Circular optimal: [1, -2, 3, -2] → max circular = 5 (wrap: [1, ..., 3])
Case 4: Non-circular optimal: [5, -1, 5] → 9 (no wrap)
Time
O(N)
Space
O(1)