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 / Op | Time | Notes |
|---|---|---|
| Array access | O(1) | By index |
| Array search | O(n) | Linear scan |
| Array sort | O(n log n) | Arrays.sort uses dual-pivot quicksort |
| HashMap get/put | O(1) avg | O(n) worst — hash collision |
| HashSet contains | O(1) avg | Same as HashMap |
| TreeMap get/put | O(log n) | Sorted, red-black tree |
| PriorityQueue add/poll | O(log n) | Heap operations |
| Stack push/pop | O(1) | Use Deque, not Stack class |
| String concat (+) | O(n) | Use StringBuilder in loops |
| Binary search | O(log n) | Array must be sorted |
| BFS / DFS | O(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.