Chapter 2

Arrays & Strings

Master foundational data structures that power modern programming. Learn array fundamentals, dynamic lists, multi-dimensional arrays, string manipulation, and algorithm patterns that solve real interview problems.

20
Topics Covered
60+
Code Examples
15+
Practice Problems
4
Core Patterns
Table of Contents
2.1

Array Fundamentals

Arrays allocate contiguous memory blocks storing elements of the same type. Understanding memory layout, indexing, and static vs dynamic arrays is crucial for writing efficient code.

Memory Layout & Indexing

Arrays store elements sequentially in memory. Each element occupies fixed size based on type. You access elements by 0-based index.

C#
// Array memory: contiguous block
int[] arr = new int[5]; // 5 ints, each 4 bytes = 20 bytes
// [arr[0]][arr[1]][arr[2]][arr[3]][arr[4]]

arr[0] = 10;
arr[4] = 50;
Console.WriteLine(arr[0]); // O(1) access - contiguous memory

Key insight: Random access is O(1) because memory is contiguous. Any element accessed instantly regardless of position.

Static vs Dynamic

Static Arrays
Fixed size at initialization
Cannot grow/shrink
O(1) access, insertion in-place
Wasted space if underutilized
Dynamic Arrays (List)
Grows automatically when full
Flexible for unknown sizes
Amortized O(1) append
Reallocation overhead

Insertion & Deletion Complexity

Access
O(1)
Search
O(n)
Insert (middle)
O(n)
Delete (middle)
O(n)
Append
O(1)*

* Amortized for dynamic arrays; requires reallocation occasionally

2.2

C# Array API

The Array class provides built-in methods for manipulation. Master these to write clean, efficient code.

Declaration & Initialization

C#
// Explicit size
int[] arr1 = new int[5]; // Uninitialized: [0,0,0,0,0]

// Array initializer
int[] arr2 = new int[] { 1, 2, 3, 4, 5 };

// Implicit size
int[] arr3 = { 10, 20, 30 };

string[] names = new string[3];
double[] prices = { 9.99, 19.99, 29.99 };

int length = arr2.Length; // 5

Array.Sort()

Sorts in-place using introsort. Average O(n log n), worst O(n log n).

C#
int[] nums = { 5, 2, 8, 1, 9 };
Array.Sort(nums);
// [1, 2, 5, 8, 9]

// Descending with custom comparator
Array.Sort(nums, Comparer<int>.Create((a, b) => b.CompareTo(a)));

// Sort range: index 1, length 3
Array.Sort(nums, 1, 3);

Array.Reverse() & Array.Copy()

C#
int[] arr = { 1, 2, 3, 4, 5 };
Array.Reverse(arr); // [5, 4, 3, 2, 1]

// Copy: defensive copying
int[] original = { 1, 2, 3, 4, 5 };
int[] copy = new int[5];
Array.Copy(original, copy, 5); // O(n)
copy[0] = 999; // original unchanged

// Partial copy
int[] partial = new int[3];
Array.Copy(original, 1, partial, 0, 3); // [2,3,4]

Array.Fill() & Array.IndexOf()

C#
// Fill with value
int[] arr = new int[5];
Array.Fill(arr, 7); // [7,7,7,7,7]

// Search: linear O(n)
int[] nums = { 5, 2, 8, 2, 9 };
int idx = Array.IndexOf(nums, 2); // 1 (first)
int lastIdx = Array.LastIndexOf(nums, 2); // 3

// Binary search on sorted array: O(log n)
Array.Sort(nums);
int pos = Array.BinarySearch(nums, 8); // Index of 8

LINQ for Arrays

C#
int[] nums = { 1, 2, 3, 4, 5 };

// Transform (map)
var squared = nums.Select(x => x * x).ToArray(); // [1,4,9,16,25]

// Filter
var evens = nums.Where(x => x % 2 == 0).ToArray(); // [2,4]

// Reduce
int sum = nums.Aggregate(0, (acc, x) => acc + x); // 15

// First/Last/Any/All
int first = nums.First();
bool hasEven = nums.Any(x => x % 2 == 0); // true
bool allPos = nums.All(x => x > 0); // true
2.3

Lists & Dynamic Arrays

List<T> provides dynamic resizing. Internally uses arrays with auto-reallocation when capacity exceeded.

List Basics

C#
// Create list
var list = new List<int>(); // Empty
var list2 = new List<int> { 1, 2, 3 }; // With values
var list3 = new List<int>(10); // Capacity hint

// Properties
int count = list.Count; // Number of elements
int capacity = list.Capacity; // Allocated space

// Access
int first = list[0]; // O(1)
list[0] = 99; // Modify in-place

Core Operations

C#
var list = new List<int>();

// Add: amortized O(1)
list.Add(1); list.Add(2); list.Add(3);

// Insert: O(n) - shifts elements
list.Insert(1, 99); // Insert 99 at index 1
// [1, 99, 2, 3]

// Remove: O(n) - shifts elements
list.Remove(99); // Remove first occurrence
list.RemoveAt(0); // Remove at index

// Clear
list.Clear(); // Empty list, count=0

Search & Contains

C#
var list = new List<int> { 5, 2, 8, 2, 9 };

// Contains: O(n)
bool has5 = list.Contains(5); // true

// IndexOf: O(n)
int idx = list.IndexOf(2); // 1 (first)
int lastIdx = list.LastIndexOf(2); // 3

// FindIndex with predicate: O(n)
int evenIdx = list.FindIndex(x => x % 2 == 0); // 1

// Find single element
int found = list.Find(x => x > 5); // 8

Sort & Binary Search

C#
var list = new List<int> { 5, 2, 8, 1, 9 };

// Sort in-place: O(n log n)
list.Sort();
// [1, 2, 5, 8, 9]

// Custom sort
list.Sort((a, b) => b.CompareTo(a)); // Descending

// Binary search on sorted list: O(log n)
list.Sort(); // Must be sorted first
int pos = list.BinarySearch(5); // Index of 5

// Capacity management
list.TrimExcess(); // Reduce capacity to count

Capacity vs Count

When capacity is exceeded, list doubles it (typical strategy). This enables amortized O(1) append.

C#
var list = new List<int>(2); // Initial capacity: 2
list.Add(1); // Count: 1, Capacity: 2
list.Add(2); // Count: 2, Capacity: 2
list.Add(3); // Reallocation! Count: 3, Capacity: 4
list.Add(4); // Count: 4, Capacity: 4
list.Add(5); // Reallocation! Count: 5, Capacity: 8
2.4

Multi-Dimensional Arrays

2D arrays represent matrices. Jagged arrays provide flexible dimensions. Both have distinct memory layouts.

2D Arrays

C#
// Rectangular 2D array: 3x4 matrix
int[,] matrix = new int[3, 4];

// With initializer
int[,] mat2 = new int[,]
{
    { 1, 2, 3, 4 },
    { 5, 6, 7, 8 },
    { 9, 10, 11, 12 }
};

// Access
int val = mat2[0, 0]; // 1
mat2[1, 2] = 99; // Set to 99

// Dimensions
int rows = mat2.GetLength(0); // 3
int cols = mat2.GetLength(1); // 4

// Iteration
for (int i = 0; i < rows; i++)
    for (int j = 0; j < cols; j++)
        Console.Write(mat2[i, j] + " ");

Jagged Arrays

Array of arrays - each row can have different length. More flexible, non-rectangular.

C#
// Jagged array: array of arrays
int[][] jagged = new int[3][]; // 3 rows, variable columns

// Initialize each row
jagged[0] = new int[2]; // Row 0: 2 elements
jagged[1] = new int[3]; // Row 1: 3 elements
jagged[2] = new int[4]; // Row 2: 4 elements

// Or with initializer
int[][] jagged2 = new int[][]
{
    new int[] { 1, 2 },
    new int[] { 3, 4, 5 },
    new int[] { 6, 7, 8, 9 }
};

// Access
int val = jagged2[1][2]; // 5
jagged2[0][0] = 99;

// Row lengths differ
int row1Len = jagged2[1].Length; // 3
int row2Len = jagged2[2].Length; // 4

2D vs Jagged Comparison

2D Arrays
Rectangular shape enforced
Contiguous memory
Slightly faster access
Simpler syntax
Jagged Arrays
Variable row lengths
Non-contiguous memory
More flexible
More memory overhead
2.5

String Fundamentals

Strings are immutable sequences of characters. Understanding immutability is crucial for performance.

Immutability in C#

Once created, strings cannot be changed. Operations return new strings.

C#
string s = "hello";
string s2 = s.ToUpper(); // "HELLO" - new string
Console.WriteLine(s); // Still "hello" - original unchanged

// Concatenation creates new string
string msg = s + " world"; // New string

// String interning: identical literals share memory
string a = "hello";
string b = "hello";
bool same = ReferenceEquals(a, b); // true - same reference

String Length & Indexing

C#
string s = "hello";
int len = s.Length; // 5

// Index: O(1)
char c = s[0]; // 'h'
char last = s[s.Length - 1]; // 'o'

// Enumerate characters
foreach (char ch in s)
    Console.Write(ch);

// Convert to char array
char[] chars = s.ToCharArray(); // O(n)
string reversed = new string(chars.Reverse()); // Wrong! Reverse in-place

// Correct reversal
Array.Reverse(chars);
string rev = new string(chars);

String Encoding & Unicode

Strings use Unicode (UTF-16 internally). Each char is 2 bytes, supporting emoji and international characters.

C#
// Unicode support
string emoji = "😊";
int len = emoji.Length; // 2! (surrogate pair)

// UTF-8 encoding for bytes
byte[] utf8 = Encoding.UTF8.GetBytes("hello");
string decoded = Encoding.UTF8.GetString(utf8);

// ASCII
byte[] ascii = Encoding.ASCII.GetBytes("hello");
2.6

C# String API

Master common string operations. All return new strings (immutable).

Substring & Search

C#
string s = "hello world";

// Substring: O(n) where n is substring length
string sub = s.Substring(0, 5); // "hello"
string rest = s.Substring(6); // "world"

// Search: O(nm) naive, optimized internally
int idx = s.IndexOf("world"); // 6
int lastIdx = s.LastIndexOf("o"); // 7

// Case-insensitive search
int idx2 = s.IndexOf("WORLD", StringComparison.OrdinalIgnoreCase); // 6

// Contains: O(nm)
bool has = s.Contains("world"); // true
bool starts = s.StartsWith("hello"); // true
bool ends = s.EndsWith("world"); // true

Replace & Split

C#
string s = "hello world hello";

// Replace all occurrences: O(nm)
string replaced = s.Replace("hello", "hi"); // "hi world hi"

// Case-insensitive replace
string csv = "a,b,c,d";
string[] parts = csv.Split(','); // ["a","b","c","d"]

// Split with options
string spaced = "a, b, c, d";
string[] clean = spaced.Split(
    new[] { ',' },
    StringSplitOptions.RemoveEmptyEntries
); // ["a", "b", "c", "d"]

// Split by multiple delimiters
string mixed = "a,b;c:d";
string[] split = mixed.Split(new[] { ',', ';', ':' });

Trim, Case Conversion, Compare

C#
string s = "  hello world  ";

// Trim: remove whitespace
string trimmed = s.Trim(); // "hello world"
string left = s.TrimStart(); // "hello world  "
string right = s.TrimEnd(); // "  hello world"

// Case conversion
string upper = s.ToUpper(); // "  HELLO WORLD  "
string lower = s.ToLower(); // "  hello world  "

// Comparison: O(n)
int cmp = s.CompareTo("hello"); // > 0 (s is greater)
bool equal = s.Equals("hello", StringComparison.OrdinalIgnoreCase);

// String.Concat & String.Join
string concat = String.Concat("hello", " ", "world");
string joined = String.Join(",", new[] { "a", "b", "c" }); // "a,b,c"
2.7

StringBuilder

StringBuilders efficiently accumulate strings. Use when repeatedly concatenating in loops.

Why StringBuilder?

String concatenation with + creates new strings each time (O(n) each). StringBuilder uses a buffer for O(1) append.

String Concatenation Loop
result = result + str creates new string each iteration
N iterations: O(n²) time complexity
Garbage collection overhead
StringBuilder
Append to buffer: O(1) amortized
N iterations: O(n) total time
Single allocation at end

Core Operations

C#
// Create StringBuilder
var sb = new StringBuilder();
var sb2 = new StringBuilder(1000); // Capacity hint

// Append: O(1) amortized
sb.Append("hello");
sb.Append(" ");
sb.Append("world");
sb.AppendLine(); // Add newline

// Insert: O(n)
sb.Insert(0, "prefix:"); // Insert at index 0

// Remove: O(n)
sb.Remove(0, 7); // Remove 7 chars starting at 0

// Replace
sb.Replace("world", "universe");

// Properties
int len = sb.Length;
int cap = sb.Capacity;

// Convert to string: O(n)
string result = sb.ToString();

Practical Example: Build CSV

C#
var sb = new StringBuilder();
var data = new[] { "a", "b", "c", "d" };

// Efficient loop: O(n)
for (int i = 0; i < data.Length; i++)
{
    sb.Append(data[i]);
    if (i < data.Length - 1)
        sb.Append(",");
}

string csv = sb.ToString(); // "a,b,c,d"

// Compare to inefficient way:
string result = ""; // O(n²) - don't do this!
for (int i = 0; i < data.Length; i++)
    result = result + data[i] + ",";
2.8

Common Patterns

Four powerful techniques solve most array/string interview problems efficiently.

1. Prefix Sum

Precompute cumulative sums for O(1) range queries.

C#
// Build prefix sum array: O(n)
int[] nums = { 1, 2, 3, 4, 5 };
int[] prefix = new int[nums.Length + 1];
for (int i = 0; i < nums.Length; i++)
    prefix[i + 1] = prefix[i] + nums[i];
// prefix: [0, 1, 3, 6, 10, 15]

// Query range [i, j): O(1)
int sum = prefix[4] - prefix[1]; // Sum of nums[1..3] = 2+3+4 = 9

2. Two Pointers

Use indices moving toward each other. Optimal for sorted arrays.

Valid Palindrome
Check alphanumeric palindrome (case-insensitive).
Two PointersO(n)
1
C#
public bool IsPalindrome(string s)
{
    int left = 0, right = s.Length - 1;
    while (left < right)
    {
        while (left < right && !char.IsLetterOrDigit(s[left]))
            left++;
        while (left < right && !char.IsLetterOrDigit(s[right]))
            right--;
        if (char.ToLower(s[left]) != char.ToLower(s[right]))
            return false;
        left++; right--;
    }
    return true;
}

3. Sliding Window

Maintain dynamic window with two pointers. Expand/contract to track condition.

Max Consecutive Ones
Find longest consecutive 1s in binary array.
Sliding WindowO(n)
2
C#
public int FindMaxConsecutiveOnes(int[] nums)
{
    int maxCount = 0, currentCount = 0;
    foreach (int num in nums)
    {
        if (num == 1)
            currentCount++;
        else
        {
            maxCount = Math.Max(maxCount, currentCount);
            currentCount = 0;
        }
    }
    return Math.Max(maxCount, currentCount);
}

4. Kadane's Algorithm

Find maximum subarray sum. Greedy approach: track max ending here and global max.

Maximum Subarray
Find contiguous subarray with max sum.
KadaneO(n)
3
C#
public int MaxSubArray(int[] nums)
{
    int maxSum = nums[0], currentSum = 0;
    foreach (int num in nums)
    {
        // Either extend current subarray or start new
        currentSum = Math.Max(num, currentSum + num);
        // Update global maximum
        maxSum = Math.Max(maxSum, currentSum);
    }
    return maxSum;
}
// Example: [-2,1,-3,4,-1,2,1,-5,4]
// Max subarray: [4,-1,2,1] = 6
2.9

Practice Problems

15+ problems with complete C# solutions. Master patterns through practice.

1. Two Sum
Find two numbers that sum to target. Return indices.
HashMapO(n)
1
C#
public int[] TwoSum(int[] nums, int target)
{
    var map = new Dictionary<int, int>();
    for (int i = 0; i < nums.Length; i++)
    {
        int complement = target - nums[i];
        if (map.ContainsKey(complement))
            return new[] { map[complement], i };
        map[nums[i]] = i;
    }
    return new int[0]; // No solution
}
2. Best Time to Buy & Sell Stock
Find max profit from one buy and sell. Must buy before sell.
GreedyO(n)
2
C#
public int MaxProfit(int[] prices)
{
    int minPrice = int.MaxValue, maxProfit = 0;
    foreach (int price in prices)
    {
        if (price < minPrice)
            minPrice = price;
        else
            maxProfit = Math.Max(maxProfit, price - minPrice);
    }
    return maxProfit;
}
3. Contains Duplicate
Check if array has duplicate elements.
HashSetO(n)
3
C#
public bool ContainsDuplicate(int[] nums)
{
    var seen = new HashSet<int>();
    foreach (int num in nums)
    {
        if (seen.Contains(num))
            return true;
        seen.Add(num);
    }
    return false;
}
4. Product of Array Except Self
Return array where result[i] = product of all except nums[i]. No division.
Prefix SumO(n)
4
C#
public int[] ProductExceptSelf(int[] nums)
{
    int n = nums.Length;
    int[] result = new int[n];
    // result[i] = product of all left
    result[0] = 1;
    for (int i = 1; i < n; i++)
        result[i] = result[i - 1] * nums[i - 1];
    // Multiply by product of all right
    int rightProduct = 1;
    for (int i = n - 1; i >= 0; i--)
    {
        result[i] *= rightProduct;
        rightProduct *= nums[i];
    }
    return result;
}
5. Move Zeroes
Move all zeros to end in-place. Maintain relative order of non-zeros.
Two PointersO(n)
5
C#
public void MoveZeroes(int[] nums)
{
    int pos = 0; // Position to place non-zero
    for (int i = 0; i < nums.Length; i++)
    {
        if (nums[i] != 0)
        {
            // Swap non-zero to position pos
            int temp = nums[pos];
            nums[pos] = nums[i];
            nums[i] = temp;
            pos++;
        }
    }
}
6. Rotate Array
Rotate array right by k positions in-place.
ReversalO(n)
6
C#
public void Rotate(int[] nums, int k)
{
    k %= nums.Length; // Handle k > length
    Reverse(nums, 0, nums.Length - 1); // Reverse all
    Reverse(nums, 0, k - 1); // Reverse first k
    Reverse(nums, k, nums.Length - 1); // Reverse rest
}

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--;
    }
}
7. Reverse String
Reverse string in-place (char array).
Two PointersO(n)
7
C#
public void ReverseString(char[] s)
{
    int left = 0, right = s.Length - 1;
    while (left < right)
    {
        char temp = s[left];
        s[left] = s[right];
        s[right] = temp;
        left++; right--;
    }
}
8. Valid Anagram
Check if two strings are anagrams (same chars, different order).
Char CountO(n)
8
C#
public bool IsAnagram(string s, string t)
{
    if (s.Length != t.Length)
        return false;
    var counts = new int[26];
    for (int i = 0; i < s.Length; i++)
    {
        counts[s[i] - 'a']++;
        counts[t[i] - 'a']--;
    }
    return counts.All(c => c == 0);
}
9. Group Anagrams
Group strings that are anagrams together.
HashMapO(n*k)
9
C#
public IList<IList<string>> GroupAnagrams(string[] strs)
{
    var map = new Dictionary<string, List<string>>();
    foreach (string s in strs)
    {
        char[] chars = s.ToCharArray();
        Array.Sort(chars);
        string key = new string(chars);
        if (!map.ContainsKey(key))
            map[key] = new List<string>();
        map[key].Add(s);
    }
    return map.Values.ToList<List<string>>();
}
10. Merge Sorted Array
Merge two sorted arrays into first array in-place. Work backwards.
Two PointersO(n+m)
10
C#
public void Merge(int[] nums1, int m, int[] nums2, int n)
{
    int p1 = m - 1, p2 = n - 1, p = m + n - 1;
    while (p1 >= 0 && p2 >= 0)
    {
        if (nums1[p1] > nums2[p2])
            nums1[p--] = nums1[p1--];
        else
            nums1[p--] = nums2[p2--];
    }
    // If p2 remains, copy rest (p1 already in place)
    while (p2 >= 0)
        nums1[p--] = nums2[p2--];
}
11. Longest Common Prefix
Find longest prefix shared by all strings.
StringO(nm)
11
C#
public string LongestCommonPrefix(string[] strs)
{
    if (strs.Length == 0)
        return "";
    for (int i = 0; i < strs[0].Length; i++)
    {
        char c = strs[0][i];
        for (int j = 1; j < strs.Length; j++)
        {
            if (i >= strs[j].Length || strs[j][i] != c)
                return strs[0].Substring(0, i);
        }
    }
    return strs[0];
}
12. String to Integer (atoi)
Convert string to integer. Handle whitespace, signs, overflow.
String ParseO(n)
12
C#
public int MyAtoi(string s)
{
    s = s.Trim();
    if (s.Length == 0)
        return 0;
    int sign = 1, result = 0, i = 0;
    if (s[0] == '-' || s[0] == '+')
    {
        sign = s[0] == '-' ? -1 : 1;
        i++;
    }
    while (i < s.Length && char.IsDigit(s[i]))
    {
        int digit = s[i] - '0';
        if (result > int.MaxValue / 10 || (result == int.MaxValue / 10 && digit > 7))
            return sign == 1 ? int.MaxValue : int.MinValue;
        result = result * 10 + digit;
        i++;
    }
    return result * sign;
}
13. Longest Palindromic Substring
Find longest substring that is palindrome. Expand-around-center approach.
Two PointersO(n²)
13
C#
public string LongestPalindrome(string s)
{
    string longest = "";
    for (int i = 0; i < s.Length; i++)
    {
        // Odd length palindromes (single center)
        string odd = ExpandAroundCenter(s, i, i);
        if (odd.Length > longest.Length)
            longest = odd;
        // Even length palindromes (two centers)
        string even = ExpandAroundCenter(s, i, i + 1);
        if (even.Length > longest.Length)
            longest = even;
    }
    return longest;
}

private string ExpandAroundCenter(string s, int left, int right)
{
    while (left >= 0 && right < s.Length && s[left] == s[right])
    {
        left--;
        right++;
    }
    return s.Substring(left + 1, right - left - 1);
}
Practice approach: Understand patterns, implement without looking, test edge cases. Focus on clean code and efficiency.
2.10

Collections Comparison

Choose the right data structure for performance. Understand trade-offs.

Structure Access Insert Delete Search Use Case
Array O(1) O(n) O(n) O(n) Random access, known size
List<T> O(1) O(n) O(n) O(n) Dynamic array, frequent append
LinkedList O(n) O(1)* O(1)* O(n) Frequent insert/delete in middle
Stack O(1) O(1) O(1) O(n) LIFO, backtracking, undo
Queue O(1) O(1) O(1) O(n) FIFO, BFS, task scheduling
HashSet N/A O(1) O(1) O(1) Unique elements, membership test
HashMap O(1) O(1) O(1) O(1) Key-value pairs, frequency count

* Assumes you have reference to the node

Typical choice: Start with List<T> or Dictionary. Only optimize if profiling shows bottleneck.
2.11

Interview Tips

TIP 1
Clarify Requirements
Ask: Can input be modified? What about empty/null inputs? Are there constraints on values (negative, duplicates)?
TIP 2
Start Simple
Get a working solution first (even O(n²)). Then optimize. Show your thinking process, not just the answer.
TIP 3
Space-Time Trade-off
Use HashSet/HashMap to reduce time from O(n²) to O(n). Cost: O(n) extra space. Usually worth it.
TIP 4
In-Place Modifications
Ask if modifying input is allowed. Saves O(n) space but changes caller's data. Confirm with interviewer.
TIP 5
Test Edge Cases
Empty array, single element, duplicates, all zeros, negative numbers. Walk through 1-2 examples by hand.
TIP 6
Sorting Strategy
Sorting is O(n log n) but often simplifies logic. If allowed, sort first—two pointers become possible.
Common mistakes: Off-by-one errors in loops. Not handling empty input. Forgetting to check bounds before accessing indices. Always validate assumptions.
Key insight: Most array/string problems use one of four patterns: prefix sum, two pointers, sliding window, or hash-based counting. Recognize the pattern, apply the technique.

Deep Dive: Pattern Analysis

Understanding which pattern applies is the key to solving quickly. Here's how to recognize each:

When to Use Prefix Sum
Problem asks for range sums or cumulative values. Single pass to build array, then O(1) queries. Works with: continuous subarrays, range sums, cumulative differences.
Example: "How many subarrays sum to target?" Build prefix, use HashMap to count matches.
When to Use Two Pointers
Array is sorted OR problem asks for pairs/triples. Move pointers based on comparison. Works with: palindromes, two-sum variants, container problems, partition problems.
Example: "Find two numbers that sum to target in sorted array" → Two pointers converge from ends.
When to Use Sliding Window
Problem involves substrings/subarrays with a condition. Maintain window size (fixed or dynamic). Works with: longest substring, min window, frequency problems.
Example: "Longest substring without repeating chars" → Expand right, contract left when duplicate found.
When to Use Hash-Based Counting
Problem involves existence, frequency, or uniqueness. Store seen values in HashMap/HashSet. Works with: duplicates, anagrams, missing elements, complement problems.
Example: "Two Sum" → Hash values seen so far, check for complement in O(1).

Advanced: String Algorithms

Beyond basic API, several specialized string algorithms appear in interviews:

KMP (Knuth-Morris-Pratt) String Matching

Find pattern in text in O(n+m) time (vs naive O(nm)). Uses failure function to avoid redundant comparisons.

C#
// Build KMP failure function
int[] BuildFailure(string pattern)
{
    int m = pattern.Length;
    int[] failure = new int[m];
    int j = 0;
    for (int i = 1; i < m; i++)
    {
        while (j > 0 && pattern[i] != pattern[j])
            j = failure[j - 1];
        if (pattern[i] == pattern[j])
            j++;
        failure[i] = j;
    }
    return failure;
}

// KMP Search: Find all occurrences of pattern in text
public IList<int> KMPSearch(string text, string pattern)
{
    var result = new List<int>();
    if (pattern.Length == 0)
        return result;
    int[] failure = BuildFailure(pattern);
    int j = 0;
    for (int i = 0; i < text.Length; i++)
    {
        while (j > 0 && text[i] != pattern[j])
            j = failure[j - 1];
        if (text[i] == pattern[j])
            j++;
        if (j == pattern.Length)
        {
            result.Add(i - pattern.Length + 1);
            j = failure[j - 1];
        }
    }
    return result;
}

Advanced Practice Problems

14. Longest Substring Without Repeating Characters
Find longest substring with all unique characters using sliding window.
Sliding WindowO(n)
14
C#
public int LengthOfLongestSubstring(string s)
{
    var lastSeen = new Dictionary<char, int>();
    int maxLen = 0, left = 0;
    for (int right = 0; right < s.Length; right++)
    {
        char c = s[right];
        // If char seen in current window, move left
        if (lastSeen.ContainsKey(c) && lastSeen[c] >= left)
            left = lastSeen[c] + 1;
        // Update last position of char
        lastSeen[c] = right;
        // Track max window size
        maxLen = Math.Max(maxLen, right - left + 1);
    }
    return maxLen;
}
15. Minimum Window Substring
Find smallest window in string containing all chars from target.
Sliding WindowO(n)
15
C#
public string MinWindow(string s, string t)
{
    if (t.Length > s.Length)
        return "";
    var targetCount = new Dictionary<char, int>();
    foreach (char c in t)
        targetCount[c] = targetCount.GetValueOrDefault(c) + 1;
    int required = targetCount.Count, formed = 0;
    var windowCount = new Dictionary<char, int>();
    int left = 0, minLen = int.MaxValue, minStart = 0;
    for (int right = 0; right < s.Length; right++)
    {
        char c = s[right];
        windowCount[c] = windowCount.GetValueOrDefault(c) + 1;
        if (targetCount.ContainsKey(c) && windowCount[c] == targetCount[c])
            formed++;
        while (formed == required && left <= right)
        {
            if (right - left + 1 < minLen)
            {
                minLen = right - left + 1;
                minStart = left;
            }
            char leftChar = s[left];
            windowCount[leftChar]--;
            if (targetCount.ContainsKey(leftChar) && windowCount[leftChar] < targetCount[leftChar])
                formed--;
            left++;
        }
    }
    return minLen == int.MaxValue ? "" : s.Substring(minStart, minLen);
}

Memory & Space Optimization Techniques

Interview questions often ask: "Can you do this in O(1) space?" Here are key techniques:

In-Place Swaps
Modify array without extra space by swapping elements. Works for: reordering, partitioning, rotating. Trade-off: Destructive to original data.
Example: Move Zeroes—use two pointers to swap, not create new array.
Bit Manipulation
Use bits to store multiple flags in single integer. Works for: marking visited, boolean arrays, storing state efficiently.
Example: Track seen numbers using bitmask instead of HashMap.
String Reversal Pattern
Reverse sections of array to achieve reordering. Technique: reverse all, then reverse parts. O(n) time, O(1) space.
Example: Rotate Array—reverse all, reverse first k, reverse rest.
Marking with Values
Use array values themselves to mark/track state. Works when values are in known range. Destructive but space-efficient.
Example: Mark seen numbers by negating at index. Valid Anagram uses char array as counter.

Common Pitfalls & Solutions

Off-by-one errors: Loop conditions, array indices, substring ranges. Write out exact boundaries: [0, n), [i, j] inclusive/exclusive. Test with small examples.
Null/empty input: Always check if array/string is empty before accessing [0] or iterating. Return early with appropriate value (empty array, empty string, -1).
Integer overflow: When building numbers from digits (atoi), check bounds before multiplying. Use long if needed temporarily.
String immutability: Every concat/replace creates new string. Use StringBuilder for loops. Forgetting costs you time complexity.

Performance Benchmarks: When to Optimize

Interview problems usually have constraints hinting at expected complexity. Here's guidance:

n ≤ 10
O(n!) brute force OK
n ≤ 100
O(n³) acceptable
n ≤ 1000
O(n²) typical
n ≤ 10⁵
O(n log n) expected
n ≤ 10⁶
O(n) or O(n log n)
n > 10⁶
O(n) linear only
Pro tip: When asked for O(1) space, it usually means: use input array itself, or clever algorithm avoiding data structures. Impossible? Say so, get clarification. Better to ask than implement wrong solution.

Real-World Application Scenarios

These data structures and patterns appear constantly in production systems:

1
Search & Filtering: Two-pointer binary search and sliding windows power database query engines. Elasticsearch and Solr use similar concepts for range filtering.
2
Text Processing: KMP and string algorithms enable efficient text editors, regex engines, diff tools. Used in every IDE and terminal.
3
Analytics & Aggregation: Prefix sums compute running totals in O(1) after preprocessing. Powers time-series databases and analytics platforms.
4
Compression & Encoding: String manipulation and character counting underpin Huffman coding, compression algorithms, text analytics.
5
Cache & Deduplication: HashSets detect duplicates, HashMap tracks frequency. Essential for caching layers, CDN edge servers, data deduplication.

Study Strategy & Time Management

You cannot memorize 15+ problems. Instead, understand patterns deeply:

Week 1: Foundations
Master basic array operations (indexing, insertion, deletion). Understand List<T> vs Array tradeoffs. Do 3 simple problems: TwoSum, ContainsDuplicate, BestTimeStock.
Week 2: Patterns
Learn prefix sum, two pointers, sliding window via dedicated problems. Do MoveZeroes, Palindrome, LengthOfLongestSubstring. Build pattern recognition.
Week 3: Integration
Solve mixed problems that require choosing patterns. Practice on LeetCode medium arrays. Get comfortable with decision-making under pressure.
Week 4: Advanced
Deep-dive into string algorithms (KMP), edge cases, optimization tricks. Mock interview with timer. Focus on communication, not just code.

String vs Array: When to Convert

Some problems are easier as char arrays, others stay as strings. Here's the decision tree:

Convert String to char[]
Need in-place modification (reverse, swap)
Need random-access writes: s[i] = 'x'
Building result and efficiency matters
Working with individual characters deeply
Keep as String
Only need to read, never modify
Using String API (Split, Replace, Contains)
Returning string result
Pattern matching with regex coming

Complexity Deep-Dive

Beyond big-O notation, understand practical constants and hidden costs:

Array vs List append: Array is truly O(1) once allocated. List is amortized O(1)—occasionally O(n) when reallocating. Average case hidden: reallocation happens ~log(n) times total, spreading O(n) cost across additions.
HashMap lookup: O(1) average, O(n) worst case (hash collisions). In practice, modern hash tables are well-tuned. Avoid worst-case by using good hash functions. Dictionary<K,V> in C# is solid.
Sorting cost: Array.Sort() is O(n log n) always (introsort). Smaller arrays? Might be faster than hashing (O(n) space but constant overhead). Measure before optimizing.

Final Checklist: Before Submitting Interview Solution

Code Quality
Readable names, no magic numbers
Correctness
Handles empty, single element, nulls
Edge Cases
Tested duplicates, negatives, boundary values
Complexity
Stated time and space clearly
Optimization
Considered space-time tradeoffs
Testing
Walked through 1-2 examples by hand
Communication
Explained approach before coding
Discussion
Asked clarifying questions at start
Improvement
Discussed what could be optimized

Key Takeaways

Master these fundamentals and you'll solve majority of array/string interview problems:

1
Understand memory layout: Arrays are contiguous, enabling O(1) random access. This foundation shapes all array algorithm design.
2
Know your collections: Array vs List vs HashMap differences drive solution choice. Pick the right tool.
3
Recognize patterns: Prefix sum, two pointers, sliding window, hashing. 90% of problems use these four approaches.
4
Practice edge cases: Empty input, duplicates, single element, negative numbers. These separate strong candidates from weak ones.
5
Optimize iteratively: Get it working first, then optimize. Communicate throughout. Interviewers care about your thinking more than the final code.