Table of Contents
- Basic Syntax & Patterns
- Arrays & Strings
- Linked Lists
- Stacks & Queues
- Trees & Graphs
- Dynamic Programming
- Sorting & Searching
- Hash Tables & Sets
- Mathematical Algorithms
- Common Patterns & Tricks
- Performance Tips & Best Practices
- Advanced Topics & Patterns
Basic Syntax & Patterns
Essential Imports
import java.util.*;
import java.util.stream.*;Common Data Types & Operations
// Primitive types - know their ranges
int x = Integer.MAX_VALUE; // 2^31 - 1
long y = Long.MAX_VALUE; // 2^63 - 1
double d = Double.MAX_VALUE;
// String operations - strings are immutable
String s = "hello";
s.charAt(0); // 'h' - O(1)
s.substring(1, 3); // "el" - O(n)
s.indexOf('e'); // 1 - O(n)
s.length(); // 5 - O(1)
// StringBuilder for mutable strings - crucial for performance
StringBuilder sb = new StringBuilder();
sb.append("hello"); // O(1) amortized
sb.toString(); // O(n)Input/Output Patterns
// Fast input reading
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
String line = sc.nextLine();
// Multiple test cases pattern
while (sc.hasNext()) {
// process input
}Arrays & Strings
Array Basics & Common Operations
// Declaration and initialization
int[] arr = new int[n]; // O(n) space
int[] arr2 = {1, 2, 3, 4, 5};
Arrays.fill(arr, -1); // Fill with value - O(n)
// Essential array methods
Arrays.sort(arr); // O(n log n)
int pos = Arrays.binarySearch(arr, target); // O(log n) - array must be sorted
Arrays.copyOf(arr, newLength); // O(n)Two Pointer Technique
Use for: Array/string problems, palindromes, pairs with target sum
// Classic two-pointer for sorted array pair sum
public int[] twoSum(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) return new int[]{left, right};
else if (sum < target) left++;
else right--;
}
return new int[]{-1, -1}; // Not found
}
// Sliding window for subarray problems
public int maxSubarray(int[] nums, int k) {
int maxSum = 0, windowSum = 0;
// Initial window
for (int i = 0; i < k; i++) {
windowSum += nums[i];
}
maxSum = windowSum;
// Slide the window
for (int i = k; i < nums.length; i++) {
windowSum += nums[i] - nums[i - k]; // Add new, remove old
maxSum = Math.max(maxSum, windowSum);
}
return maxSum;
}String Processing Patterns
// Character frequency counting - O(n) time, O(1) space for ASCII
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) return false;
int[] count = new int[26]; // For lowercase letters only
for (int i = 0; i < s.length(); i++) {
count[s.charAt(i) - 'a']++;
count[t.charAt(i) - 'a']--;
}
for (int c : count) {
if (c != 0) return false;
}
return true;
}
// String reversal and palindrome check
public boolean isPalindrome(String s) {
int left = 0, right = s.length() - 1;
while (left < right) {
while (left < right && !Character.isLetterOrDigit(s.charAt(left))) left++;
while (left < right && !Character.isLetterOrDigit(s.charAt(right))) right--;
if (Character.toLowerCase(s.charAt(left)) !=
Character.toLowerCase(s.charAt(right))) {
return false;
}
left++;
right--;
}
return true;
}Linked Lists
Node Definition & Basic Operations
// Singly linked list node
class ListNode {
int val;
ListNode next;
ListNode(int val) { this.val = val; }
}
// Doubly linked list node
class DoublyListNode {
int val;
DoublyListNode prev, next;
DoublyListNode(int val) { this.val = val; }
}Essential Linked List Patterns
// Reverse a linked list - O(n) time, O(1) space
public ListNode reverseList(ListNode head) {
ListNode prev = null, curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
// Fast & slow pointer for cycle detection (Floyd's algorithm)
public boolean hasCycle(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true;
}
return false;
}
// Find middle of linked list
public ListNode findMiddle(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow; // For even length, returns second middle
}
// Merge two sorted lists - O(m + n) time, O(1) space
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode curr = dummy;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
curr.next = l1;
l1 = l1.next;
} else {
curr.next = l2;
l2 = l2.next;
}
curr = curr.next;
}
curr.next = (l1 != null) ? l1 : l2;
return dummy.next;
}Stacks & Queues
Stack Implementation & Use Cases
// Stack declaration
Stack<Integer> stack = new Stack<>();
Deque<Integer> stack2 = new ArrayDeque<>(); // Preferred over Stack class
// Basic operations - all O(1)
stack.push(element);
int top = stack.pop();
int peek = stack.peek();
boolean isEmpty = stack.isEmpty();
// Valid parentheses pattern - O(n) time, O(n) space
public boolean isValidParentheses(String s) {
Stack<Character> stack = new Stack<>();
Map<Character, Character> mapping = new HashMap<>();
mapping.put(')', '(');
mapping.put('}', '{');
mapping.put(']', '[');
for (char c : s.toCharArray()) {
if (mapping.containsKey(c)) { // Closing bracket
if (stack.isEmpty() || stack.pop() != mapping.get(c)) {
return false;
}
} else { // Opening bracket
stack.push(c);
}
}
return stack.isEmpty();
}Queue Implementation & BFS Pattern
// Queue declaration
Queue<Integer> queue = new LinkedList<>();
Deque<Integer> deque = new ArrayDeque<>(); // More efficient
// Basic operations
queue.offer(element); // Add to rear - O(1)
int front = queue.poll(); // Remove from front - O(1)
int peek = queue.peek(); // Look at front - O(1)
// Level-order traversal template (BFS)
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) return result;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int levelSize = queue.size();
List<Integer> currentLevel = new ArrayList<>();
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll();
currentLevel.add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
result.add(currentLevel);
}
return result;
}Monotonic Stack/Queue Patterns
// Next greater element using monotonic stack - O(n) time
public int[] nextGreaterElement(int[] nums) {
int n = nums.length;
int[] result = new int[n];
Arrays.fill(result, -1);
Stack<Integer> stack = new Stack<>(); // Store indices
for (int i = 0; i < n; i++) {
while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
result[stack.pop()] = nums[i];
}
stack.push(i);
}
return result;
}
// Sliding window maximum using deque - O(n) time
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> deque = new ArrayDeque<>(); // Store indices
int[] result = new int[nums.length - k + 1];
int ri = 0;
for (int i = 0; i < nums.length; i++) {
// Remove indices outside current window
while (!deque.isEmpty() && deque.peekFirst() <= i - k) {
deque.pollFirst();
}
// Maintain decreasing order in deque
while (!deque.isEmpty() && nums[deque.peekLast()] <= nums[i]) {
deque.pollLast();
}
deque.offerLast(i);
if (i >= k - 1) {
result[ri++] = nums[deque.peekFirst()];
}
}
return result;
}Trees & Graphs
Binary Tree Node & Traversals
// Binary tree node definition
class TreeNode {
int val;
TreeNode left, right;
TreeNode(int val) { this.val = val; }
}
// Recursive traversals - O(n) time, O(h) space where h is height
public void inorderTraversal(TreeNode root, List<Integer> result) {
if (root != null) {
inorderTraversal(root.left, result); // Left
result.add(root.val); // Root
inorderTraversal(root.right, result); // Right
}
}
public void preorderTraversal(TreeNode root, List<Integer> result) {
if (root != null) {
result.add(root.val); // Root
preorderTraversal(root.left, result); // Left
preorderTraversal(root.right, result); // Right
}
}
public void postorderTraversal(TreeNode root, List<Integer> result) {
if (root != null) {
postorderTraversal(root.left, result); // Left
postorderTraversal(root.right, result); // Right
result.add(root.val); // Root
}
}
// Iterative inorder using stack
public List<Integer> inorderIterative(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode curr = root;
while (curr != null || !stack.isEmpty()) {
while (curr != null) {
stack.push(curr);
curr = curr.left;
}
curr = stack.pop();
result.add(curr.val);
curr = curr.right;
}
return result;
}Binary Search Tree Operations
// BST search - O(h) time where h is height
public TreeNode search(TreeNode root, int val) {
if (root == null || root.val == val) return root;
return val < root.val ? search(root.left, val) : search(root.right, val);
}
// BST insertion - O(h) time
public TreeNode insert(TreeNode root, int val) {
if (root == null) return new TreeNode(val);
if (val < root.val) {
root.left = insert(root.left, val);
} else {
root.right = insert(root.right, val);
}
return root;
}
// Validate BST - O(n) time, O(h) space
public boolean isValidBST(TreeNode root) {
return validate(root, null, null);
}
private boolean validate(TreeNode node, Integer min, Integer max) {
if (node == null) return true;
if ((min != null && node.val <= min) || (max != null && node.val >= max)) {
return false;
}
return validate(node.left, min, node.val) && validate(node.right, node.val, max);
}Graph Representations & Traversals
// Adjacency list representation
Map<Integer, List<Integer>> adjList = new HashMap<>();
// DFS recursive - O(V + E) time, O(V) space
public void dfs(int node, Set<Integer> visited, Map<Integer, List<Integer>> graph) {
visited.add(node);
System.out.print(node + " ");
for (int neighbor : graph.getOrDefault(node, new ArrayList<>())) {
if (!visited.contains(neighbor)) {
dfs(neighbor, visited, graph);
}
}
}
// BFS using queue - O(V + E) time, O(V) space
public void bfs(int start, Map<Integer, List<Integer>> graph) {
Set<Integer> visited = new HashSet<>();
Queue<Integer> queue = new LinkedList<>();
visited.add(start);
queue.offer(start);
while (!queue.isEmpty()) {
int node = queue.poll();
System.out.print(node + " ");
for (int neighbor : graph.getOrDefault(node, new ArrayList<>())) {
if (!visited.contains(neighbor)) {
visited.add(neighbor);
queue.offer(neighbor);
}
}
}
}
// Shortest path in unweighted graph using BFS
public int shortestPath(int[][] grid, int[] start, int[] target) {
int m = grid.length, n = grid[0].length;
boolean[][] visited = new boolean[m][n];
Queue<int[]> queue = new LinkedList<>();
queue.offer(new int[]{start[0], start[1], 0}); // {row, col, distance}
visited[start[0]][start[1]] = true;
int[][] directions = {{-1,0}, {1,0}, {0,-1}, {0,1}}; // Up, Down, Left, Right
while (!queue.isEmpty()) {
int[] curr = queue.poll();
int row = curr[0], col = curr[1], dist = curr[2];
if (row == target[0] && col == target[1]) {
return dist;
}
for (int[] dir : directions) {
int newRow = row + dir[0];
int newCol = col + dir[1];
if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n &&
grid[newRow][newCol] != 1 && !visited[newRow][newCol]) {
visited[newRow][newCol] = true;
queue.offer(new int[]{newRow, newCol, dist + 1});
}
}
}
return -1; // Path not found
}Dynamic Programming
Classic DP Patterns & Examples
1. Fibonacci Sequence - Bottom-up Approach
// O(n) time, O(1) space
public int fibonacci(int n) {
if (n <= 1) return n;
int prev2 = 0, prev1 = 1;
for (int i = 2; i <= n; i++) {
int curr = prev1 + prev2;
prev2 = prev1;
prev1 = curr;
}
return prev1;
}2. Longest Increasing Subsequence - O(n²) DP
public int lengthOfLIS(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
Arrays.fill(dp, 1); // Each element forms LIS of length 1
int maxLength = 1;
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
maxLength = Math.max(maxLength, dp[i]);
}
return maxLength;
}3. Coin Change - Unbounded Knapsack
// Minimum coins to make amount - O(amount * coins) time
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1); // Initialize with impossible value
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int coin : coins) {
if (i >= coin) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}4. Longest Common Subsequence - 2D DP
// O(m * n) time and space
public int longestCommonSubsequence(String text1, String text2) {
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++) {
if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}5. Maximum Subarray Sum - Kadane’s Algorithm
// O(n) time, O(1) space
public int maxSubArray(int[] nums) {
int maxSoFar = nums[0];
int maxEndingHere = nums[0];
for (int i = 1; i < nums.length; i++) {
maxEndingHere = Math.max(nums[i], maxEndingHere + nums[i]);
maxSoFar = Math.max(maxSoFar, maxEndingHere);
}
return maxSoFar;
}Memoization Template
// Top-down DP with memoization
private Map<String, Integer> memo = new HashMap<>();
public int solveWithMemo(int[] params) {
String key = Arrays.toString(params); // Create unique key
if (memo.containsKey(key)) return memo.get(key);
// Base cases
if (/* base condition */) return /* base value */;
// Recursive calls
int result = /* compute result using recursive calls */;
memo.put(key, result);
return result;
}Sorting & Searching
Built-in Sorting
// Array sorting - O(n log n)
Arrays.sort(arr); // Primitive arrays
Arrays.sort(objArray); // Object arrays (must implement Comparable)
Arrays.sort(arr, Collections.reverseOrder()); // Reverse order for Integer[]
// Custom comparator
Arrays.sort(intervals, (a, b) -> a[0] - b[0]); // Sort by first element
// List sorting
Collections.sort(list);
list.sort((a, b) -> a - b); // Using lambda
list.sort(Comparator.comparingInt(x -> x)); // Method referenceQuick Sort Implementation
// O(n log n) average, O(n²) worst case
public void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
private int partition(int[] arr, int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
swap(arr, i, j);
}
}
swap(arr, i + 1, high);
return i + 1;
}Merge Sort Implementation
// O(n log n) time, O(n) space - stable sort
public void mergeSort(int[] arr, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
private void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int i = left, j = mid + 1, k = 0;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) temp[k++] = arr[i++];
while (j <= right) temp[k++] = arr[j++];
System.arraycopy(temp, 0, arr, left, temp.length);
}Binary Search Variations
// Standard binary search - O(log n)
public int binarySearch(int[] arr, int target) {
int left = 0, right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // Avoid overflow
if (arr[mid] == target) return mid;
else if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
// Find first occurrence (leftmost)
public int findFirst(int[] arr, int target) {
int left = 0, right = arr.length - 1;
int result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
result = mid;
right = mid - 1; // Continue searching left
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
}
// Find last occurrence (rightmost)
public int findLast(int[] arr, int target) {
int left = 0, right = arr.length - 1;
int result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
result = mid;
left = mid + 1; // Continue searching right
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
}Hash Tables & Sets
HashMap & HashSet Usage
// HashMap - O(1) average time for all operations
Map<String, Integer> map = new HashMap<>();
map.put("key", value); // Insert/update
Integer val = map.get("key"); // Retrieve
boolean exists = map.containsKey("key"); // Check existence
map.remove("key"); // Delete
map.getOrDefault("key", 0); // Get with default value
// Iterate over map
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
}
// HashSet - O(1) average time for add, remove, contains
Set<Integer> set = new HashSet<>();
set.add(element); // Add element
boolean exists = set.contains(element); // Check membership
set.remove(element); // Remove elementCommon Hash Table Patterns
// Two Sum using HashMap - O(n) time, O(n) space
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>(); // value -> index
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[]{map.get(complement), i};
}
map.put(nums[i], i);
}
return new int[]{-1, -1};
}
// Group Anagrams - O(n * m log m) where n is number of strings, m is max length
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (String str : strs) {
char[] chars = str.toCharArray();
Arrays.sort(chars);
String key = new String(chars);
map.computeIfAbsent(key, k -> new ArrayList<>()).add(str);
}
return new ArrayList<>(map.values());
}
// Longest Substring Without Repeating Characters - Sliding Window + HashSet
public int lengthOfLongestSubstring(String s) {
Set<Character> seen = new HashSet<>();
int left = 0, maxLength = 0;
for (int right = 0; right < s.length(); right++) {
while (seen.contains(s.charAt(right))) {
seen.remove(s.charAt(left));
left++;
}
seen.add(s.charAt(right));
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}Mathematical Algorithms
Number Theory & Arithmetic
// GCD using Euclidean algorithm - O(log min(a, b))
public int gcd(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
// LCM using GCD
public int lcm(int a, int b) {
return (a * b) / gcd(a, b);
}
// Fast exponentiation - O(log n)
public long power(long base, long exp, long mod) {
long result = 1;
base %= mod;
while (exp > 0) {
if ((exp & 1) == 1) {
result = (result * base) % mod;
}
base = (base * base) % mod;
exp >>= 1;
}
return result;
}
// Check if number is prime - O(√n)
public boolean isPrime(int n) {
if (n <= 1) return false;
if (n <= 3) return true;
if (n % 2 == 0 || n % 3 == 0) return false;
for (int i = 5; i * i <= n; i += 6) {
if (n % i == 0 || n % (i + 2) == 0) return false;
}
return true;
}
// Sieve of Eratosthenes - find all primes up to n - O(n log log n)
public List<Integer> sieveOfEratosthenes(int n) {
boolean[] isPrime = new boolean[n + 1];
Arrays.fill(isPrime, true);
isPrime[0] = isPrime[1] = false;
for (int i = 2; i * i <= n; i++) {
if (isPrime[i]) {
for (int j = i * i; j <= n; j += i) {
isPrime[j] = false;
}
}
}
List<Integer> primes = new ArrayList<>();
for (int i = 2; i <= n; i++) {
if (isPrime[i]) primes.add(i);
}
return primes;
}Bit Manipulation
// Basic bit operations
int setBit = n | (1 << i); // Set i-th bit
int clearBit = n & ~(1 << i); // Clear i-th bit
int toggleBit = n ^ (1 << i); // Toggle i-th bit
boolean isSet = (n & (1 << i)) != 0; // Check if i-th bit is set
// Count set bits (Brian Kernighan's algorithm) - O(number of set bits)
public int countSetBits(int n) {
int count = 0;
while (n != 0) {
n &= (n - 1); // Remove rightmost set bit
count++;
}
return count;
}
// Find single number (all others appear twice) - O(n) time, O(1) space
public int singleNumber(int[] nums) {
int result = 0;
for (int num : nums) {
result ^= num; // XOR cancels out pairs
}
return result;
}
// Check if power of 2 - O(1)
public boolean isPowerOfTwo(int n) {
return n > 0 && (n & (n - 1)) == 0;
}Common Patterns & Tricks
Prefix Sum Technique
// Range sum queries in O(1) after O(n) preprocessing
class PrefixSum {
private int[] prefixSum;
public PrefixSum(int[] nums) {
prefixSum = new int[nums.length + 1];
for (int i = 0; i < nums.length; i++) {
prefixSum[i + 1] = prefixSum[i] + nums[i];
}
}
public int rangeSum(int left, int right) { // Both inclusive
return prefixSum[right + 1] - prefixSum[left];
}
}
// Subarray sum equals K - O(n) time using prefix sum + HashMap
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> prefixSumCount = new HashMap<>();
prefixSumCount.put(0, 1); // Empty subarray has sum 0
int count = 0, prefixSum = 0;
for (int num : nums) {
prefixSum += num;
count += prefixSumCount.getOrDefault(prefixSum - k, 0);
prefixSumCount.put(prefixSum, prefixSumCount.getOrDefault(prefixSum, 0) + 1);
}
return count;
}Union-Find (Disjoint Set)
// Efficient for connectivity problems - O(α(n)) per operation with path compression
class UnionFind {
private int[] parent, rank;
private int components;
public UnionFind(int n) {
parent = new int[n];
rank = new int[n];
components = n;
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
public int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // Path compression
}
return parent[x];
}
public boolean union(int x, int y) {
int rootX = find(x), rootY = find(y);
if (rootX == rootY) return false;
// Union by rank
if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else {
parent[rootY] = rootX;
rank[rootX]++;
}
components--;
return true;
}
public boolean connected(int x, int y) {
return find(x) == find(y);
}
public int getComponents() {
return components;
}
}Backtracking Template
// General backtracking pattern for permutations/combinations
public List<List<Integer>> backtrack(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[] used = new boolean[nums.length];
backtrackHelper(nums, path, used, result);
return result;
}
private void backtrackHelper(int[] nums, List<Integer> path,
boolean[] used, List<List<Integer>> result) {
// Base case - valid solution found
if (path.size() == nums.length) {
result.add(new ArrayList<>(path)); // Make copy!
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) continue; // Skip used elements
// Make choice
path.add(nums[i]);
used[i] = true;
// Recurse
backtrackHelper(nums, path, used, result);
// Backtrack (undo choice)
path.remove(path.size() - 1);
used[i] = false;
}
}
// N-Queens problem example
public List<List<String>> solveNQueens(int n) {
List<List<String>> result = new ArrayList<>();
char[][] board = new char[n][n];
for (int i = 0; i < n; i++) {
Arrays.fill(board[i], '.');
}
backtrackQueens(board, 0, result);
return result;
}
private void backtrackQueens(char[][] board, int row, List<List<String>> result) {
if (row == board.length) {
result.add(constructBoard(board));
return;
}
for (int col = 0; col < board.length; col++) {
if (isValidQueenPlacement(board, row, col)) {
board[row][col] = 'Q';
backtrackQueens(board, row + 1, result);
board[row][col] = '.'; // Backtrack
}
}
}
private boolean isValidQueenPlacement(char[][] board, int row, int col) {
int n = board.length;
// Check column
for (int i = 0; i < row; i++) {
if (board[i][col] == 'Q') return false;
}
// Check diagonal (top-left to bottom-right)
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q') return false;
}
// Check diagonal (top-right to bottom-left)
for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
if (board[i][j] == 'Q') return false;
}
return true;
}Important Constants & Limits
// Integer limits
int maxInt = Integer.MAX_VALUE; // 2,147,483,647 (2^31 - 1)
int minInt = Integer.MIN_VALUE; // -2,147,483,648 (-2^31)
long maxLong = Long.MAX_VALUE; // 9,223,372,036,854,775,807 (2^63 - 1)
// Common modulo for large number problems
final int MOD = 1_000_000_007;
// Direction vectors for grid traversal
int[][] directions = {{-1,0}, {1,0}, {0,-1}, {0,1}}; // Up, Down, Left, Right
int[][] diagonals = {{-1,-1}, {-1,1}, {1,-1}, {1,1}}; // Four diagonals
// For 8-directional movement (including diagonals)
int[][] eightDirections = {{-1,-1}, {-1,0}, {-1,1}, {0,-1},
{0,1}, {1,-1}, {1,0}, {1,1}};Performance Tips & Best Practices
Time Complexity Quick Reference
- Constant: O(1) - Hash table operations, array access
- Logarithmic: O(log n) - Binary search, balanced tree operations
- Linear: O(n) - Single loop, linear search
- Linearithmic: O(n log n) - Efficient sorting, divide & conquer
- Quadratic: O(n²) - Nested loops, bubble sort
- Exponential: O(2ⁿ) - Naive recursive solutions, subset generation
Memory Optimization
// Use primitive collections when possible (avoid autoboxing)
// TIntArrayList instead of ArrayList<Integer> (if using external libraries)
// Reuse objects to avoid GC pressure
StringBuilder sb = new StringBuilder(); // Reuse instead of creating new ones
// Use arrays instead of ArrayList when size is known
int[] arr = new int[n]; // More memory efficient than ArrayList<Integer>
// Clear collections after use in long-running algorithms
map.clear();
list.clear();Common Pitfalls to Avoid
- Integer Overflow: Use
longfor large calculations - Array Index Bounds: Always check
i < arr.length - Null Pointer: Check for null before dereferencing
- Infinite Loops: Ensure loop variables are updated correctly
- Stack Overflow: Limit recursion depth, use iterative solutions when possible
- Off-by-One Errors: Be careful with array indices and loop bounds
Quick Debug Tips
// Print array contents
System.out.println(Arrays.toString(arr));
// Print 2D array
for (int[] row : matrix) {
System.out.println(Arrays.toString(row));
}
// Assert for testing assumptions
assert condition : "Error message";
// Use meaningful variable names and add comments for complex logicAdvanced Topics & Patterns
Heap (Priority Queue) Operations
// Min-heap (default)
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
// Max-heap
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
// Custom comparator for objects
PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> a[0] - b[0]);
// Top K elements pattern - O(n log k)
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> count = new HashMap<>();
for (int num : nums) {
count.put(num, count.getOrDefault(num, 0) + 1);
}
PriorityQueue<Integer> heap = new PriorityQueue<>(
(a, b) -> count.get(a) - count.get(b) // Min heap by frequency
);
for (int num : count.keySet()) {
heap.offer(num);
if (heap.size() > k) {
heap.poll();
}
}
return heap.stream().mapToInt(i -> i).toArray();
}
// Merge K sorted lists using heap - O(n log k)
public ListNode mergeKLists(ListNode[] lists) {
PriorityQueue<ListNode> heap = new PriorityQueue<>((a, b) -> a.val - b.val);
for (ListNode list : lists) {
if (list != null) heap.offer(list);
}
ListNode dummy = new ListNode(0);
ListNode curr = dummy;
while (!heap.isEmpty()) {
ListNode node = heap.poll();
curr.next = node;
curr = curr.next;
if (node.next != null) {
heap.offer(node.next);
}
}
return dummy.next;
}Trie (Prefix Tree) Implementation
// Trie for word storage and prefix search
class TrieNode {
TrieNode[] children = new TrieNode[26]; // For lowercase letters
boolean isWord = false;
}
class Trie {
private TrieNode root;
public Trie() {
root = new TrieNode();
}
// Insert word - O(m) where m is word length
public void insert(String word) {
TrieNode curr = root;
for (char c : word.toCharArray()) {
int index = c - 'a';
if (curr.children[index] == null) {
curr.children[index] = new TrieNode();
}
curr = curr.children[index];
}
curr.isWord = true;
}
// Search word - O(m)
public boolean search(String word) {
TrieNode node = searchPrefix(word);
return node != null && node.isWord;
}
// Check if prefix exists - O(m)
public boolean startsWith(String prefix) {
return searchPrefix(prefix) != null;
}
private TrieNode searchPrefix(String prefix) {
TrieNode curr = root;
for (char c : prefix.toCharArray()) {
int index = c - 'a';
if (curr.children[index] == null) {
return null;
}
curr = curr.children[index];
}
return curr;
}
}Segment Tree for Range Queries
// Segment tree for range sum queries with updates - O(log n) per operation
class SegmentTree {
private int[] tree;
private int n;
public SegmentTree(int[] nums) {
n = nums.length;
tree = new int[4 * n];
build(nums, 0, 0, n - 1);
}
private void build(int[] nums, int node, int start, int end) {
if (start == end) {
tree[node] = nums[start];
} else {
int mid = start + (end - start) / 2;
build(nums, 2 * node + 1, start, mid);
build(nums, 2 * node + 2, mid + 1, end);
tree[node] = tree[2 * node + 1] + tree[2 * node + 2];
}
}
public void update(int idx, int val) {
update(0, 0, n - 1, idx, val);
}
private void update(int node, int start, int end, int idx, int val) {
if (start == end) {
tree[node] = val;
} else {
int mid = start + (end - start) / 2;
if (idx <= mid) {
update(2 * node + 1, start, mid, idx, val);
} else {
update(2 * node + 2, mid + 1, end, idx, val);
}
tree[node] = tree[2 * node + 1] + tree[2 * node + 2];
}
}
public int query(int l, int r) {
return query(0, 0, n - 1, l, r);
}
private int query(int node, int start, int end, int l, int r) {
if (r < start || end < l) return 0; // No overlap
if (l <= start && end <= r) return tree[node]; // Complete overlap
// Partial overlap
int mid = start + (end - start) / 2;
return query(2 * node + 1, start, mid, l, r) +
query(2 * node + 2, mid + 1, end, l, r);
}
}Topological Sort (Kahn’s Algorithm)
// Topological sort for DAG - O(V + E)
public int[] topologicalSort(int numCourses, int[][] prerequisites) {
List<List<Integer>> graph = new ArrayList<>();
int[] indegree = new int[numCourses];
// Initialize graph
for (int i = 0; i < numCourses; i++) {
graph.add(new ArrayList<>());
}
// Build graph and calculate indegrees
for (int[] pre : prerequisites) {
graph.get(pre[1]).add(pre[0]); // pre[1] -> pre[0]
indegree[pre[0]]++;
}
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (indegree[i] == 0) {
queue.offer(i);
}
}
int[] result = new int[numCourses];
int index = 0;
while (!queue.isEmpty()) {
int node = queue.poll();
result[index++] = node;
for (int neighbor : graph.get(node)) {
indegree[neighbor]--;
if (indegree[neighbor] == 0) {
queue.offer(neighbor);
}
}
}
return index == numCourses ? result : new int[0]; // Check for cycle
}Advanced String Algorithms
KMP (Knuth-Morris-Pratt) Pattern Matching
// KMP string matching - O(n + m) where n is text length, m is pattern length
public int strStr(String haystack, String needle) {
if (needle.isEmpty()) return 0;
int[] lps = computeLPS(needle);
int i = 0, j = 0;
while (i < haystack.length()) {
if (haystack.charAt(i) == needle.charAt(j)) {
i++;
j++;
}
if (j == needle.length()) {
return i - j; // Found pattern
} else if (i < haystack.length() && haystack.charAt(i) != needle.charAt(j)) {
if (j != 0) {
j = lps[j - 1];
} else {
i++;
}
}
}
return -1;
}
// Compute Longest Prefix Suffix array
private int[] computeLPS(String pattern) {
int[] lps = new int[pattern.length()];
int len = 0, i = 1;
while (i < pattern.length()) {
if (pattern.charAt(i) == pattern.charAt(len)) {
len++;
lps[i] = len;
i++;
} else {
if (len != 0) {
len = lps[len - 1];
} else {
lps[i] = 0;
i++;
}
}
}
return lps;
}Dijkstra’s Shortest Path Algorithm
// Single-source shortest path - O((V + E) log V)
public int[] dijkstra(int[][] graph, int src) {
int n = graph.length;
int[] dist = new int[n];
Arrays.fill(dist, Integer.MAX_VALUE);
dist[src] = 0;
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]); // {node, distance}
pq.offer(new int[]{src, 0});
boolean[] visited = new boolean[n];
while (!pq.isEmpty()) {
int[] curr = pq.poll();
int u = curr[0];
if (visited[u]) continue;
visited[u] = true;
for (int v = 0; v < n; v++) {
if (graph[u][v] != 0 && !visited[v]) { // Edge exists
int newDist = dist[u] + graph[u][v];
if (newDist < dist[v]) {
dist[v] = newDist;
pq.offer(new int[]{v, newDist});
}
}
}
}
return dist;
}Matrix Manipulation Patterns
// Rotate matrix 90 degrees clockwise - O(n²) time, O(1) space
public void rotate(int[][] matrix) {
int n = matrix.length;
// Transpose
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
// Reverse each row
for (int i = 0; i < n; i++) {
for (int j = 0; j < n / 2; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[i][n - 1 - j];
matrix[i][n - 1 - j] = temp;
}
}
}
// Spiral matrix traversal - O(m * n)
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> result = new ArrayList<>();
if (matrix.length == 0) return result;
int top = 0, bottom = matrix.length - 1;
int left = 0, right = matrix[0].length - 1;
while (top <= bottom && left <= right) {
// Traverse right
for (int col = left; col <= right; col++) {
result.add(matrix[top][col]);
}
top++;
// Traverse down
for (int row = top; row <= bottom; row++) {
result.add(matrix[row][right]);
}
right--;
// Traverse left
if (top <= bottom) {
for (int col = right; col >= left; col--) {
result.add(matrix[bottom][col]);
}
bottom--;
}
// Traverse up
if (left <= right) {
for (int row = bottom; row >= top; row--) {
result.add(matrix[row][left]);
}
left++;
}
}
return result;
}Interval Problems
// Merge overlapping intervals - O(n log n)
public int[][] merge(int[][] intervals) {
if (intervals.length <= 1) return intervals;
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
List<int[]> merged = new ArrayList<>();
for (int[] interval : intervals) {
if (merged.isEmpty() || merged.get(merged.size() - 1)[1] < interval[0]) {
merged.add(interval); // No overlap
} else {
// Overlap - merge intervals
merged.get(merged.size() - 1)[1] =
Math.max(merged.get(merged.size() - 1)[1], interval[1]);
}
}
return merged.toArray(new int[merged.size()][]);
}
// Insert new interval - O(n)
public int[][] insert(int[][] intervals, int[] newInterval) {
List<int[]> result = new ArrayList<>();
int i = 0;
// Add all intervals before newInterval
while (i < intervals.length && intervals[i][1] < newInterval[0]) {
result.add(intervals[i]);
i++;
}
// Merge overlapping intervals with newInterval
while (i < intervals.length && intervals[i][0] <= newInterval[1]) {
newInterval[0] = Math.min(newInterval[0], intervals[i][0]);
newInterval[1] = Math.max(newInterval[1], intervals[i][1]);
i++;
}
result.add(newInterval);
// Add remaining intervals
while (i < intervals.length) {
result.add(intervals[i]);
i++;
}
return result.toArray(new int[result.size()][]);
}LRU Cache Implementation
// LRU Cache using HashMap + Doubly Linked List - O(1) for all operations
class LRUCache {
class DLLNode {
int key, value;
DLLNode prev, next;
DLLNode(int key, int value) {
this.key = key;
this.value = value;
}
}
private Map<Integer, DLLNode> cache = new HashMap<>();
private int capacity;
private DLLNode head, tail;
public LRUCache(int capacity) {
this.capacity = capacity;
head = new DLLNode(0, 0);
tail = new DLLNode(0, 0);
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLLNode node = cache.get(key);
if (node == null) return -1;
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLLNode node = cache.get(key);
if (node == null) {
DLLNode newNode = new DLLNode(key, value);
if (cache.size() >= capacity) {
DLLNode tail = removeTail();
cache.remove(tail.key);
}
cache.put(key, newNode);
addToHead(newNode);
} else {
node.value = value;
moveToHead(node);
}
}
private void addToHead(DLLNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(DLLNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(DLLNode node) {
removeNode(node);
addToHead(node);
}
private DLLNode removeTail() {
DLLNode lastNode = tail.prev;
removeNode(lastNode);
return lastNode;
}
}