Arrays & Strings
The foundation. 80% of problems touch arrays in some way. Know these cold.
Array basics O(1) access
int[] arr = new int[5];           // fixed size, all 0s
int[] arr = {1, 2, 3, 4, 5};      // inline init
arr.length                          // NOT .length() — no parens

// Iterate
for (int i = 0; i < arr.length; i++) { }
for (int num : arr) { }             // enhanced for — use when no index needed

// 2D array
int[][] grid = new int[3][4];       // 3 rows, 4 cols
grid.length                         // rows
grid[0].length                      // cols

// Fill
Arrays.fill(arr, -1);              // fill all with -1
Arrays.fill(grid[0], 0);           // fill one row

// Sort
Arrays.sort(arr);                   // ascending
Arrays.sort(arr, 0, 5);            // sort subarray [0,5)

// Copy
int[] copy = Arrays.copyOf(arr, arr.length);
int[] sub  = Arrays.copyOfRange(arr, 1, 4); // [1,4)

// Convert to list (read-only!)
List<Integer> list = Arrays.asList(1, 2, 3);
⚠ Common trap Arrays.asList() returns a fixed-size list — you CANNOT add/remove from it. Wrap it: new ArrayList<>(Arrays.asList(...)) if you need a mutable list.
Complexity cheatsheet
Know these before you write a single line. Aim for O(n) or O(n log n) in interviews.
Structure / OpTimeNotes
Array accessO(1)By index
Array searchO(n)Linear scan
Array sortO(n log n)Arrays.sort uses dual-pivot quicksort
HashMap get/putO(1) avgO(n) worst — hash collision
HashSet containsO(1) avgSame as HashMap
TreeMap get/putO(log n)Sorted, red-black tree
PriorityQueue add/pollO(log n)Heap operations
Stack push/popO(1)Use Deque, not Stack class
String concat (+)O(n)Use StringBuilder in loops
Binary searchO(log n)Array must be sorted
BFS / DFSO(V + E)V = vertices, E = edges
ArrayList
Dynamic array. Use when you need a resizable array. Your go-to for most problems.
ArrayList — everything you need
List<Integer> list = new ArrayList<>();

// Add
list.add(5);                         // append to end — O(1) amortized
list.add(0, 99);                    // insert at index 0 — O(n), SLOW

// Access
list.get(0);                         // O(1)
list.set(0, 10);                    // update at index — O(1)
list.size();                         // NOT .length

// Remove
list.remove(0);                     // remove by INDEX — O(n)
list.remove(Integer.valueOf(5));   // remove by VALUE — wrap int!

// Search
list.contains(5);                  // O(n)
list.indexOf(5);                   // -1 if not found

// Iterate — 3 ways
for (int x : list) { }              // enhanced for — cleanest
for (int i = 0; i < list.size(); i++) { list.get(i); }
list.forEach(x -> System.out.println(x));

// Sort
Collections.sort(list);             // ascending
list.sort(Comparator.reverseOrder()); // descending
list.sort((a, b) -> b - a);         // same — descending with lambda

// Useful ops
Collections.reverse(list);
Collections.max(list);
Collections.min(list);
Collections.frequency(list, 5);    // count of 5

// Convert to array
int[] arr = list.stream().mapToInt(Integer::intValue).toArray();

// Sublist (view, not copy)
List<Integer> sub = list.subList(1, 3); // [1,3)
🔴 Critical trap list.remove(1) removes by INDEX. list.remove(Integer.valueOf(1)) removes by VALUE. With int lists this causes bugs that are very hard to spot. Always wrap when removing by value.
HashMap
Your most powerful DSA tool. Frequency counting, memoization, index storing — use HashMap everywhere.
HashMap — complete reference
Map<String, Integer> map = new HashMap<>();

// Put / Get
map.put("key", 1);
map.get("key");                    // returns null if missing — not 0!
map.getOrDefault("key", 0);      // safe get — use this always

// Check
map.containsKey("key");
map.containsValue(1);             // O(n) — rarely useful
map.size();
map.isEmpty();

// Remove
map.remove("key");

// Frequency count pattern — most common use
for (char c : s.toCharArray()) {
    map.put(c, map.getOrDefault(c, 0) + 1);
}
// Shortcut — same thing
map.merge(c, 1, Integer::sum);

// putIfAbsent
map.putIfAbsent("key", 0);        // only puts if key not present

// computeIfAbsent — for nested maps / grouping
map.computeIfAbsent("key", k -> new ArrayList<>()).add("value");

// Iterate — 3 ways
for (Map.Entry<String, Integer> e : map.entrySet()) {
    e.getKey(); e.getValue();
}
for (String key : map.keySet()) { }
for (int val : map.values()) { }

// Sort by value (common interview need)
map.entrySet().stream()
   .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
   .forEach(e -> { });
✦ Pattern: Two Sum Store value → index in map. For each num, check if (target - num) exists in map. O(n) instead of O(n²).
✦ Pattern: Anagram check Build freq map for s1, decrement for s2. If all values are 0, they are anagrams. Or just sort both strings and compare.
LinkedHashMap — when order matters
// LinkedHashMap preserves insertion order
Map<String, Integer> lmap = new LinkedHashMap<>();

// LRU Cache trick — access-order LinkedHashMap
Map<Integer, Integer> lru = new LinkedHashMap<>(16, 0.75f, true) {
    protected boolean removeEldestEntry(Map.Entry e) {
        return size() > capacity;
    }
};
HashSet
When you need O(1) lookup and don't care about order or count. Duplicate detection, visited tracking.
HashSet — all ops
Set<Integer> set = new HashSet<>();

set.add(5);                         // returns false if already exists
set.contains(5);                   // O(1) — this is the whole point
set.remove(5);
set.size();

// Init from array — very common
Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3));

// Duplicate check pattern
boolean hasDup(int[] nums) {
    Set<Integer> seen = new HashSet<>();
    for (int n : nums)
        if (!seen.add(n)) return true; // add returns false if dup
    return false;
}

// Longest consecutive sequence — O(n)
Set<Integer> set = new HashSet<>();
for (int n : nums) set.add(n);
for (int n : nums) {
    if (!set.contains(n - 1)) { // start of sequence
        int len = 1;
        while (set.contains(n + len)) len++;
    }
}
Stack / Deque
Never use Stack class — it's legacy and synchronized. Always use Deque with ArrayDeque.
Deque as Stack (LIFO)
Deque<Integer> stack = new ArrayDeque<>();

stack.push(5);                      // add to top
stack.pop();                        // remove from top — throws if empty
stack.peek();                       // look at top — throws if empty
stack.isEmpty();
stack.size();

// Safe versions — return null instead of throwing
stack.offerFirst(5);               // push
stack.pollFirst();                  // pop — returns null if empty
stack.peekFirst();                  // peek — returns null if empty

// Valid parentheses pattern
Deque<Character> stk = new ArrayDeque<>();
for (char c : s.toCharArray()) {
    if (c == '(') stk.push(c);
    else if (!stk.isEmpty()) stk.pop();
    else return false;
}
return stk.isEmpty();
Deque as Queue (FIFO) — for BFS
Deque<Integer> queue = new ArrayDeque<>();

queue.offer(5);                    // enqueue — add to back
queue.poll();                      // dequeue — remove from front (null if empty)
queue.peek();                      // look at front

// Deque as double-ended (sliding window maximum)
Deque<Integer> dq = new ArrayDeque<>();
dq.offerFirst(5);   dq.offerLast(3);
dq.pollFirst();      dq.pollLast();
dq.peekFirst();      dq.peekLast();
PriorityQueue (Heap)
For Top K problems, shortest path, median finding. Min-heap by default.
PriorityQueue — min heap and max heap
// Min heap (default) — smallest element at head
PriorityQueue<Integer> minH = new PriorityQueue<>();

// Max heap
PriorityQueue<Integer> maxH = new PriorityQueue<>(Comparator.reverseOrder());
// or lambda:
PriorityQueue<Integer> maxH = new PriorityQueue<>((a, b) -> b - a);

minH.offer(5);                    // add — O(log n)
minH.poll();                      // remove min — O(log n)
minH.peek();                      // look at min — O(1)

// Top K frequent elements pattern
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]);
for (var e : freqMap.entrySet()) {
    pq.offer(new int[]{e.getKey(), e.getValue()});
    if (pq.size() > k) pq.poll();   // keep only top K
}

// Custom object in PQ
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
pq.offer(new int[]{dist, node});
✦ Top K pattern Use min-heap of size K. When size exceeds K, poll (removes smallest). At end, heap contains K largest. This is O(n log k) — better than sorting O(n log n).
TreeMap / TreeSet
Sorted map/set. Use when you need ordered keys or floor/ceiling operations. O(log n) everything.
TreeMap — sorted map with navigation
TreeMap<Integer, String> tm = new TreeMap<>();
tm.put(1, "a"); tm.put(3, "c"); tm.put(5, "e");

tm.firstKey();                     // 1 — smallest
tm.lastKey();                      // 5 — largest
tm.floorKey(4);                   // 3 — largest key <= 4
tm.ceilingKey(4);                 // 5 — smallest key >= 4
tm.lowerKey(3);                   // 1 — strictly less than 3
tm.higherKey(3);                  // 5 — strictly greater than 3

// TreeSet — same but just keys
TreeSet<Integer> ts = new TreeSet<>();
ts.floor(4);  ts.ceiling(4);
Sliding Window
When the problem asks for a subarray/substring that satisfies some condition. Converts O(n²) to O(n).
✦ When to use Keywords: "subarray", "substring", "contiguous", "window", "longest", "shortest", "maximum sum". The window expands right and shrinks from left.
Fixed window size
// Max sum of subarray of size k
int sum = 0, max = 0;
for (int i = 0; i < nums.length; i++) {
    sum += nums[i];
    if (i >= k) sum -= nums[i - k];  // shrink from left
    if (i >= k - 1) max = Math.max(max, sum);
}
Variable window — longest with condition
// Longest substring without repeating characters
Map<Character, Integer> map = new HashMap<>();
int left = 0, max = 0;
for (int right = 0; right < s.length(); right++) {
    char c = s.charAt(right);
    if (map.containsKey(c))
        left = Math.max(left, map.get(c) + 1); // shrink
    map.put(c, right);
    max = Math.max(max, right - left + 1);
}
Two Pointers
Two indices moving toward each other or in the same direction. Sorted arrays, pair finding.
✦ When to use Sorted array + find pair/triplet with target sum. Remove duplicates in-place. Merge two sorted arrays. Palindrome check.
Opposite direction — two sum sorted
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--;
}
Same direction — remove duplicates in-place
int slow = 0;
for (int fast = 1; fast < nums.length; fast++) {
    if (nums[fast] != nums[slow])
        nums[++slow] = nums[fast];
}
return slow + 1; // new length
Fast & Slow Pointers
Floyd's cycle detection. For linked list problems — cycle, middle node, duplicate number.
Cycle detection + middle of linked list
// Detect cycle
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow == fast) return true; // cycle!
}
return false;

// Find middle (slow is at middle when fast reaches end)
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
    slow = slow.next;
    fast = fast.next.next;
}
// slow is now at middle
Binary Search
Not just for sorted arrays. Binary search on the ANSWER is a powerful pattern you must know.
Standard binary search template
int left = 0, right = nums.length - 1;
while (left <= right) {              // <= not <
    int mid = left + (right - left) / 2; // avoid overflow — never (l+r)/2
    if      (nums[mid] == target) return mid;
    else if (nums[mid] < target)  left = mid + 1;
    else                          right = mid - 1;
}
return -1;
Binary search on answer — find minimum valid value
// "What is the minimum X such that condition(X) is true?"
// Example: Koko eating bananas, capacity ship packages
int left = minPossible, right = maxPossible;
while (left < right) {               // < not <=
    int mid = left + (right - left) / 2;
    if (canDo(mid)) right = mid;       // mid works, try smaller
    else           left = mid + 1;    // mid too small
}
return left;
🔴 Never write (left + right) / 2 It overflows for large values. Always use left + (right - left) / 2. This is a classic interview trap.
BFS / DFS
BFS = shortest path, level order. DFS = all paths, cycle detection, connected components.
BFS — level order / shortest path
Deque<Integer> queue = new ArrayDeque<>();
Set<Integer> visited = new HashSet<>();
queue.offer(start);
visited.add(start);
int level = 0;

while (!queue.isEmpty()) {
    int size = queue.size();           // process level by level
    for (int i = 0; i < size; i++) {
        int node = queue.poll();
        if (node == target) return level;
        for (int nei : graph.get(node)) {
            if (!visited.contains(nei)) {
                visited.add(nei);
                queue.offer(nei);
            }
        }
    }
    level++;
}
DFS — recursive and iterative
// Recursive DFS
void dfs(int node, Set<Integer> visited) {
    visited.add(node);
    for (int nei : graph.get(node)) {
        if (!visited.contains(nei))
            dfs(nei, visited);
    }
}

// Grid DFS (islands pattern)
int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}};
void dfs(char[][] grid, int r, int c) {
    if (r < 0 || r >= grid.length || c < 0 ||
        c >= grid[0].length || grid[r][c] != '1') return;
    grid[r][c] = '0';                 // mark visited by mutating
    for (int[] d : dirs)
        dfs(grid, r + d[0], c + d[1]);
}
Dynamic Programming
You already have 50 DP problems solved. This is a refresh of the core patterns.
Pattern
1D DP
dp[i] depends on dp[i-1] or dp[i-2]. Climbing stairs, house robber, coin change.
dp[i] = dp[i-1] + dp[i-2]
Pattern
2D DP
dp[i][j] depends on previous row/col. Unique paths, edit distance, LCS.
dp[i][j] = dp[i-1][j] + dp[i][j-1]
Pattern
Knapsack
0/1 choices at each step. Subset sum, partition equal subset, target sum.
dp[i][w] = max(dp[i-1][w], val+dp[i-1][w-wt])
Pattern
String DP
Comparing two strings. LCS, edit distance, palindrome substrings.
if s1[i]==s2[j]: dp[i][j]=dp[i-1][j-1]+1
DP template — bottom up
// Coin change — minimum coins to make amount
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1);        // fill with "infinity"
dp[0] = 0;
for (int a = 1; a <= amount; a++)
    for (int c : coins)
        if (c <= a)
            dp[a] = Math.min(dp[a], dp[a - c] + 1);
return dp[amount] > amount ? -1 : dp[amount];
Monotonic Stack
Next greater/smaller element problems. Histogram area. Daily temperatures. O(n) solution to O(n²) problems.
✦ When to use "Next greater element", "previous smaller element", "largest rectangle in histogram", "trapping rain water".
Next greater element
int[] result = new int[nums.length];
Arrays.fill(result, -1);
Deque<Integer> stack = new ArrayDeque<>(); // stores indices

for (int i = 0; i < nums.length; i++) {
    while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
        result[stack.pop()] = nums[i]; // found next greater
    }
    stack.push(i);
}
Sorting tricks
Custom sort — lambdas
// Sort int array descending — must use Integer[]
Integer[] arr = {3,1,4,1,5};
Arrays.sort(arr, (a, b) -> b - a);

// Sort 2D array by first element
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);

// Sort by length then alphabetically
Arrays.sort(words, (a, b) ->
    a.length() != b.length() ? a.length() - b.length() : a.compareTo(b));

// WARNING: b - a overflows with large negatives. Safe version:
(a, b) -> Integer.compare(b, a)
String tricks
String operations — all the ones that matter
String s = "hello";

s.charAt(0);                        // 'h'
s.length();                         // 5 — with parens, unlike array
s.substring(1, 3);                  // "el" — [1,3)
s.indexOf('l');                     // 2 — first occurrence
s.contains("ell");
s.startsWith("he");
s.endsWith("lo");
s.toCharArray();                    // char[] — use in loops
s.split(",");                       // String[]
s.trim();                           // remove leading/trailing spaces
s.toLowerCase(); s.toUpperCase();
s.replace('l', 'r');              // replaces ALL
String.valueOf(42);                 // int to String
Integer.parseInt("42");            // String to int

// Char arithmetic
char c = 'a';
c - 'a';                            // 0 — index in alphabet
(char)(c + 1);                      // 'b'
Character.isLetter(c);
Character.isDigit(c);
Character.toLowerCase(c);

// StringBuilder — use in loops, not + concatenation
StringBuilder sb = new StringBuilder();
sb.append("hi");
sb.reverse();
sb.deleteCharAt(0);
sb.toString();

// Frequency array (only lowercase letters)
int[] freq = new int[26];
for (char c : s.toCharArray()) freq[c - 'a']++;
// Faster than HashMap for lowercase-only problems
🔴 Never use == to compare strings s1 == s2 compares references. Always use s1.equals(s2). This trips people up constantly in interviews.
Math tricks
Math operations you'll actually need
Math.max(a, b);  Math.min(a, b);
Math.abs(a);
Math.pow(2, 10);                    // returns double
Math.sqrt(25);                      // returns double
(int) Math.sqrt(25);                // cast to int when needed

// Integer limits — use these, not magic numbers
Integer.MAX_VALUE;                   // 2^31 - 1 = 2147483647
Integer.MIN_VALUE;                   // -2^31 = -2147483648
Long.MAX_VALUE;                      // when int overflows

// Modulo (always positive)
((a % m) + m) % m;                   // safe mod for negative numbers

// Even / Odd
n % 2 == 0;                         // even
(n & 1) == 0;                        // even — bitwise, slightly faster

// Divide rounding up
(int) Math.ceil((double) a / b);
(a + b - 1) / b;                    // integer ceiling division

// Bit tricks
n & (n - 1);                        // removes lowest set bit (check power of 2: == 0)
n & (-n);                            // isolates lowest set bit
Integer.bitCount(n);                // count set bits
Interview habits
The meta-skills that separate candidates who know the same things.
✦ Before writing any code Repeat the problem in your own words. Give 1–2 examples including edge cases. State your approach and time/space complexity BEFORE coding. Ask about constraints (array size, negative numbers, duplicates).
✦ Edge cases to always mention Empty input. Single element. All same elements. Negative numbers. Integer overflow. Already sorted. Reverse sorted.
⚠ Pattern recognition — ask yourself Is it sorted? → Binary search or two pointers. Subarray/substring? → Sliding window. Tree/graph? → BFS or DFS. Optimal substructure? → DP. Next greater/smaller? → Monotonic stack. Top K? → Heap.
🔴 Common mistakes in interviews Off-by-one in binary search. Not checking null/empty. Using int when long needed. Modifying input array without asking. Not handling the case where answer doesn't exist. Returning wrong type.
✦ When you're stuck — say this out loud "Let me think about the brute force first." Then optimize. Interviewers want to see your thinking, not silence.