DDSA Solutions

C# DSA Cheat Sheet

Quick reference for C# data structures and algorithmic patterns used in LeetCode and coding interviews. All snippets are idiomatic .NET — tested on LeetCode's C# judge.

1. Data Structures

Dictionary (Hash Map)

var freq = new Dictionary<int, int>();
foreach (var x in nums)
    freq[x] = freq.GetValueOrDefault(x) + 1;

// Check and get
if (freq.TryGetValue(key, out int val)) { /* use val */ }

// Iterate
foreach (var (k, v) in freq) { }

O(1) average lookup/insert. Use for frequency counting, complement lookup (Two Sum), and caching seen values.

HashSet

var seen = new HashSet<int>();
seen.Add(x);
bool exists = seen.Contains(x);
seen.Remove(x);

// Intersection / union
var inter = new HashSet<int>(a);
inter.IntersectWith(b);

O(1) membership. Use to track visited nodes, detect duplicates, and set operations.

Stack

var stack = new Stack<int>();
stack.Push(x);
int top = stack.Peek();   // look without removing
int val = stack.Pop();    // remove and return
bool empty = stack.Count == 0;

LIFO. Use for balanced parentheses, monotonic stack, DFS iterative, undo operations.

Queue

var q = new Queue<int>();
q.Enqueue(x);
int front = q.Peek();
int val = q.Dequeue();
bool empty = q.Count == 0;

FIFO. Use for BFS, level-order tree traversal, sliding window with constraints.

Priority Queue (Min-Heap)

// Min-heap (.NET 6+)
var pq = new PriorityQueue<int, int>();
pq.Enqueue(value, priority);     // lower priority = dequeued first
int top = pq.Peek().Element;     // not available — use Dequeue
(int elem, int pri) = pq.Dequeue();
int size = pq.Count;

// Max-heap: negate the priority
pq.Enqueue(value, -value);

O(log n) push/pop. Use for top-K, Dijkstra, merge K sorted lists, median stream (two heaps).

SortedSet / SortedDictionary

var ss = new SortedSet<int>();
ss.Add(x);
int min = ss.Min;
int max = ss.Max;
int floor  = ss.GetViewBetween(int.MinValue, x).Max;  // largest ≤ x
int ceiling = ss.GetViewBetween(x, int.MaxValue).Min; // smallest ≥ x
ss.Remove(x);

O(log n) all ops. Use when you need both fast lookup and order queries (floor/ceiling/successor).

Linked List (as Deque)

var dq = new LinkedList<int>();
dq.AddFirst(x);   // push front
dq.AddLast(x);    // push back
int front = dq.First!.Value;
int back  = dq.Last!.Value;
dq.RemoveFirst();
dq.RemoveLast();

O(1) front/back ops. Use for sliding window max/min (monotonic deque) and LRU cache.

2. Sorting & Searching

// Sort array ascending
Array.Sort(arr);

// Sort with custom comparator (sort by first element)
Array.Sort(intervals, (a, b) => a[0] - b[0]);

// Sort descending
Array.Sort(arr, (a, b) => b - a);

// LINQ sort (stable, returns IEnumerable)
var sorted = arr.OrderBy(x => x).ToArray();
var sortedDesc = arr.OrderByDescending(x => x).ToArray();
var sortedByLen = words.OrderBy(w => w.Length).ThenBy(w => w).ToArray();

// Sort List<T>
list.Sort((a, b) => a.CompareTo(b));

Array.Sort uses introsort (O(n log n)). Not stable — use LINQ OrderBy when stability matters.

// Binary search on sorted array
int idx = Array.BinarySearch(arr, target);
// idx >= 0 → found; idx < 0 → ~idx is insertion point

// Manual binary search
int lo = 0, hi = arr.Length - 1;
while (lo <= hi) {
    int mid = lo + (hi - lo) / 2;   // avoids overflow
    if (arr[mid] == target) return mid;
    if (arr[mid] < target) lo = mid + 1;
    else hi = mid - 1;
}

3. Two Pointers

Opposite ends (sorted array)

int left = 0, right = nums.Length - 1;
while (left < right) {
    int sum = nums[left] + nums[right];
    if (sum == target) return new[] { left, right };
    if (sum < target) left++;
    else right--;
}

Fast / Slow pointer (cycle detection)

var slow = head;
var fast = head;
while (fast != null && fast.next != null) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow == fast) return true; // cycle
}
return false;

Remove duplicates in-place

int k = 1;
for (int i = 1; i < nums.Length; i++)
    if (nums[i] != nums[i - 1])
        nums[k++] = nums[i];
return k;

4. Sliding Window

Fixed size window

int windowSum = 0;
for (int i = 0; i < k; i++) windowSum += nums[i];
int maxSum = windowSum;
for (int i = k; i < nums.Length; i++) {
    windowSum += nums[i] - nums[i - k];
    maxSum = Math.Max(maxSum, windowSum);
}

Variable size — at most K distinct characters

var freq = new Dictionary<char, int>();
int left = 0, maxLen = 0;
for (int right = 0; right < s.Length; right++) {
    freq[s[right]] = freq.GetValueOrDefault(s[right]) + 1;
    while (freq.Count > k) {                      // shrink
        freq[s[left]]--;
        if (freq[s[left]] == 0) freq.Remove(s[left]);
        left++;
    }
    maxLen = Math.Max(maxLen, right - left + 1);
}

6. BFS (Shortest Path / Level Order)

Grid BFS

int[][] dirs = { new[]{0,1}, new[]{0,-1}, new[]{1,0}, new[]{-1,0} };
var q = new Queue<(int r, int c)>();
q.Enqueue((startR, startC));
var visited = new bool[rows, cols];
visited[startR, startC] = true;
int steps = 0;

while (q.Count > 0) {
    int size = q.Count;
    for (int i = 0; i < size; i++) {
        var (r, c) = q.Dequeue();
        if (r == endR && c == endC) return steps;
        foreach (var d in dirs) {
            int nr = r + d[0], nc = c + d[1];
            if (nr >= 0 && nr < rows && nc >= 0 && nc < cols
                && !visited[nr, nc] && grid[nr][nc] != '#') {
                visited[nr, nc] = true;
                q.Enqueue((nr, nc));
            }
        }
    }
    steps++;
}

Tree Level Order

var result = new List<IList<int>>();
if (root == null) return result;
var q = new Queue<TreeNode>();
q.Enqueue(root);
while (q.Count > 0) {
    int size = q.Count;
    var level = new List<int>();
    for (int i = 0; i < size; i++) {
        var node = q.Dequeue();
        level.Add(node.val);
        if (node.left != null)  q.Enqueue(node.left);
        if (node.right != null) q.Enqueue(node.right);
    }
    result.Add(level);
}

7. DFS / Backtracking

Recursive DFS on graph

var visited = new HashSet<int>();
void Dfs(int node) {
    visited.Add(node);
    foreach (var neighbour in graph[node])
        if (!visited.Contains(neighbour))
            Dfs(neighbour);
}

Backtracking template (subsets / permutations)

var result = new List<IList<int>>();
void Backtrack(int start, List<int> current) {
    result.Add(new List<int>(current)); // snapshot
    for (int i = start; i < nums.Length; i++) {
        current.Add(nums[i]);       // choose
        Backtrack(i + 1, current);  // explore
        current.RemoveAt(current.Count - 1); // un-choose
    }
}
Backtrack(0, new List<int>());

8. Dynamic Programming

1D DP — Climbing Stairs / House Robber pattern

// dp[i] = best answer considering first i elements
int[] dp = new int[n + 1];
dp[0] = base0;
dp[1] = base1;
for (int i = 2; i <= n; i++)
    dp[i] = Math.Max(dp[i - 1], dp[i - 2] + nums[i - 1]);
return dp[n];

// Space-optimised (rolling variables)
int prev2 = base0, prev1 = base1;
for (int i = 2; i <= n; i++) {
    int cur = Math.Max(prev1, prev2 + nums[i - 1]);
    prev2 = prev1; prev1 = cur;
}
return prev1;

2D DP — Longest Common Subsequence pattern

int m = text1.Length, n = text2.Length;
int[,] dp = new int[m + 1, n + 1];
for (int i = 1; i <= m; i++)
    for (int j = 1; j <= n; j++)
        dp[i, j] = text1[i-1] == text2[j-1]
            ? dp[i-1, j-1] + 1
            : Math.Max(dp[i-1, j], dp[i, j-1]);
return dp[m, n];

Memoization with tuple key

var memo = new Dictionary<(int, int), int>();
int Solve(int i, int j) {
    if (/* base case */) return 0;
    if (memo.TryGetValue((i, j), out int cached)) return cached;
    int result = /* recurrence */;
    return memo[(i, j)] = result;
}

9. Bit Manipulation

// Check if k-th bit is set (0-indexed from right)
bool isSet = (n & (1 << k)) != 0;

// Set k-th bit
n |= (1 << k);

// Clear k-th bit
n &= ~(1 << k);

// Toggle k-th bit
n ^= (1 << k);

// Count set bits (popcount)
int count = BitOperations.PopCount((uint)n); // .NET 5+

// Clear lowest set bit
n &= n - 1;

// Isolate lowest set bit
int lsb = n & (-n);

// Check power of two
bool isPow2 = n > 0 && (n & (n - 1)) == 0;

// XOR trick: find single number in pairs
int result = 0;
foreach (var x in nums) result ^= x;

10. Useful .NET APIs

// String operations
string rev = new string(s.Reverse().ToArray());
char[] arr = s.ToCharArray();
string joined = string.Join(",", list);
string[] parts = s.Split(' ', StringSplitOptions.RemoveEmptyEntries);
bool starts = s.StartsWith("abc");
string sub = s.Substring(start, length); // or s[start..(start+length)]

// Math
int abs = Math.Abs(x);
int gcd = (int)BigInteger.GreatestCommonDivisor(a, b); // using System.Numerics
int log2 = (int)Math.Log2(n);  // floor
int sqrt = (int)Math.Sqrt(n);  // floor — always verify: (long)sqrt*sqrt <= n

// Array utilities
Array.Fill(arr, 0);
Array.Reverse(arr);
int idx = Array.BinarySearch(arr, target);

// List utilities
list.Sort();
list.Reverse();
list.RemoveAt(list.Count - 1);  // pop back

// Char helpers
char.IsLetter(c)
char.IsDigit(c)
char.ToLower(c)
int digit = c - '0';
int idx   = c - 'a';  // 0-based alphabet index