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.
Contents
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);
}5. Binary Search on Answer Space
// Template: "find minimum X such that condition(X) is true"
// condition must be monotonically false then true
int lo = minPossible, hi = maxPossible;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (condition(mid)) hi = mid; // mid might be the answer
else lo = mid + 1;
}
return lo; // lo == hi == answer
// Example: minimum capacity to ship within D days
bool canShip(int capacity) {
int days = 1, cur = 0;
foreach (var w in weights) {
if (cur + w > capacity) { days++; cur = 0; }
cur += w;
}
return days <= D;
}Key insight: the answer space is monotonic — once a capacity works, all larger capacities also work. Binary search finds the boundary.
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