Two Sum, equal character frequencies, remove element in place, even sums after queries, find all anagrams, longest substring without repeats, merge sorted lists, rotate a matrix, move zeroes and find missing numbers — patterns, code and complexity.
Published September 25, 2026
These are LeetCode-style problems that show up in fresher rounds at product companies. Each one maps to a pattern: hashing, two pointers, sliding window, or in-place marking. Name the pattern first. It shows you're reasoning, not reciting a memorised answer.
Pattern: a hash map of "value → index" (a complement lookup). One pass, O(n) time, O(n) space.
static int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> indexOf = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
Integer j = indexOf.get(target - nums[i]); // have we seen the complement?
if (j != null) return new int[] { j, i };
indexOf.put(nums[i], i); // store after the check, so an element doesn't pair with itself
}
throw new IllegalArgumentException("no pair sums to " + target);
}
Key points to cover:
Learn it in depth → Two Sum
Pattern: a frequency count, then check that all the non-zero counts are equal. O(n) time, O(1) space (26 counters).
static boolean areOccurrencesEqual(String s) {
int[] count = new int[26];
for (char c : s.toCharArray()) count[c - 'a']++;
int expected = 0;
for (int f : count) {
if (f == 0) continue;
if (expected == 0) expected = f;
else if (f != expected) return false;
}
return true;
}
// "abacbc" → true (each appears twice); "aaabb" → false
Key points to cover:
int[26] trick assumes lowercase a–z. For arbitrary characters, use a Map<Character, Integer>, then check new HashSet<>(map.values()).size() == 1.Pattern: two pointers, reading and writing. The reader scans every element, and the writer copies forward only the elements we keep. O(n) time, O(1) space.
static int removeElement(int[] nums, int val) {
int write = 0;
for (int read = 0; read < nums.length; read++) {
if (nums[read] != val) nums[write++] = nums[read];
}
return write; // the first 'write' elements are the result; the rest don't matter
}
Key points to cover:
Learn it in depth → Remove Duplicates from Sorted Array
[val, index] (add val to nums[index]), return the sum of the even numbers.Pattern: maintain a running total incrementally instead of re-summing the array after every query. O(n + q) instead of O(n·q).
static int[] sumEvenAfterQueries(int[] nums, int[][] queries) {
int evenSum = 0;
for (int n : nums) if (n % 2 == 0) evenSum += n;
int[] answer = new int[queries.length];
for (int i = 0; i < queries.length; i++) {
int val = queries[i][0], idx = queries[i][1];
if (nums[idx] % 2 == 0) evenSum -= nums[idx]; // remove the old contribution
nums[idx] += val;
if (nums[idx] % 2 == 0) evenSum += nums[idx]; // add the new contribution
answer[i] = evenSum;
}
return answer;
}
Key points to cover:
% 2 == 0 is correct for negative numbers too (−4 % 2 is 0). The odd check is the one to write as % 2 != 0.p's anagrams in s.Pattern: a fixed-size sliding window with character counts. Slide a window of length p.length() across s, updating the counts in O(1) per step. O(n) time overall.
static List<Integer> findAnagrams(String s, String p) {
List<Integer> result = new ArrayList<>();
if (p.length() > s.length()) return result;
int[] need = new int[26], window = new int[26];
for (char c : p.toCharArray()) need[c - 'a']++;
for (int right = 0; right < s.length(); right++) {
window[s.charAt(right) - 'a']++; // add the new character
int left = right - p.length() + 1;
if (left > 0) window[s.charAt(left - 1) - 'a']--; // drop the character that left the window
if (left >= 0 && Arrays.equals(window, need)) result.add(left);
}
return result;
}
// s = "cbaebabacd", p = "abc" → [0, 6]
Key points to cover:
Arrays.equals on 26 counters is O(26), which is constant. A "matches" counter removes even that, but it's harder to get right under pressure.Learn it in depth → Sliding Window
Pattern: a variable-size sliding window. Expand to the right. When a character repeats, move left past its previous occurrence. O(n) time.
static int lengthOfLongestSubstring(String s) {
int[] lastSeen = new int[128]; // last index + 1 for each ASCII char (0 = unseen)
int best = 0;
for (int left = 0, right = 0; right < s.length(); right++) {
char c = s.charAt(right);
left = Math.max(left, lastSeen[c]); // jump past the previous occurrence
lastSeen[c] = right + 1;
best = Math.max(best, right - left + 1);
}
return best;
}
// "abcabcbb" → 3 ("abc"), "pwwkew" → 3 ("wke")
Key points to cover:
int[128] table assumes ASCII. For full Unicode, use a Map<Character, Integer>.Learn it in depth → Longest Substring Without Repeating Characters
Pattern: a dummy head, with two pointers. Always attach the smaller node, then append whatever is left over. O(m + n) time, O(1) extra space, because we relink the existing nodes.
static ListNode mergeTwoLists(ListNode a, ListNode b) {
ListNode dummy = new ListNode(0), tail = dummy;
while (a != null && b != null) {
if (a.val <= b.val) { tail.next = a; a = a.next; } // <= keeps the merge stable
else { tail.next = b; b = b.next; }
tail = tail.next;
}
tail.next = (a != null) ? a : b;
return dummy.next;
}
Key points to cover:
Learn it in depth → Merge Two Sorted Lists
Pattern: transpose, then reverse each row. O(n²) time, O(1) extra space.
static void rotate(int[][] m) {
int n = m.length;
for (int i = 0; i < n; i++) // transpose: swap across the main diagonal
for (int j = i + 1; j < n; j++) {
int t = m[i][j]; m[i][j] = m[j][i]; m[j][i] = t;
}
for (int[] row : m) // reverse each row
for (int l = 0, r = n - 1; l < r; l++, r--) {
int t = row[l]; row[l] = row[r]; row[r] = t;
}
}
// [[1,2,3],[4,5,6],[7,8,9]] → [[7,4,1],[8,5,2],[9,6,3]]
Key points to cover:
Pattern: two pointers again. Compact the non-zero elements forward, then fill the remainder with zeros. O(n) time, O(1) space.
static void moveZeroes(int[] nums) {
int write = 0;
for (int n : nums) if (n != 0) nums[write++] = n;
while (write < nums.length) nums[write++] = 0;
}
// [0, 1, 0, 3, 12] → [1, 3, 12, 0, 0]
Key points to cover:
swap(nums, write++, read) when nums[read] != 0) does fewer writes when there are few zeros.n numbers in the range [1, n], find the numbers in that range that don't appear.Pattern: in-place marking. Use the sign of nums[v - 1] to record that value v was seen. O(n) time, O(1) extra space (not counting the output).
static List<Integer> findDisappearedNumbers(int[] nums) {
for (int n : nums) {
int idx = Math.abs(n) - 1;
if (nums[idx] > 0) nums[idx] = -nums[idx]; // mark value idx+1 as seen
}
List<Integer> missing = new ArrayList<>();
for (int i = 0; i < nums.length; i++) if (nums[i] > 0) missing.add(i + 1);
return missing;
}
// [4, 3, 2, 7, 8, 2, 3, 1] → [5, 6]
Key points to cover:
boolean[] if mutation isn't allowed.Learn it in depth → Cyclic Sort
Q: How do you recognise when a sliding window applies? A: The problem asks about a contiguous subarray or substring, and a property (a sum, distinct characters, or character counts) can be updated incrementally as the window grows or shrinks.
Q: When is a hash map better than sorting, for pair problems? A: When you need the original indices, or O(n) time. Sorting loses the positions (unless you sort index pairs), and costs O(n log n). But once sorted, two pointers use O(1) extra space.
Q: How do you find the single missing number in 0..n?
A: The expected sum n(n+1)/2 minus the actual sum (use long to avoid overflow), or XOR all the indices and values together. Both are O(n) time and O(1) space.
Q: How would you test these functions?
A: Cover the empty input, a single element, all-duplicate input, no valid answer, negative numbers where they're allowed, and the largest allowed size. Parameterised JUnit tests (@ParameterizedTest with @MethodSource) keep them compact.