Rohit Kumar
•
19 April 2025
•
18 mins
Coding patterns are a set of techniques or strategies that can be applied to solve common problems in programming. They help you recognize the underlying structure of a problem and apply the appropriate solution.
public class SlidingWindow {
public static String longestSubstring(String s) {
int left = 0, right = 0;
int maxLength = 0;
String result = "";
while (right < s.length()) {
// Expand the window
right++;
// Contract the window
while (/* condition to contract */) {
left++;
}
// Update the result
if (right - left > maxLength) {
maxLength = right - left;
result = s.substring(left, right);
}
}
return result;
}
}
Template in Java to solve Two pointers problems:
public class TwoPointers {
public int[] twoSum(int[] numbers, int target) {
int left = 0;
int right = numbers.length - 1;
while (left < right) {
int sum = numbers[left] + numbers[right];
if (sum == target) {
return new int[]{left, right};
} else if (sum < target) {
left++;
} else {
right--;
}
}
return new int[]{-1, -1}; // Not found
}
}
public class Permutations {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
backtrack(result, new ArrayList<>(), nums);
return result;
}
private void backtrack(List<List<Integer>> result, List<Integer> tempList, int[] nums) {
if (tempList.size() == nums.length) { // Base case: all numbers are used
result.add(new ArrayList<>(tempList));
return;
}
for (int i = 0; i < nums.length; i++) {
if (tempList.contains(nums[i])) continue; // Skip duplicates
tempList.add(nums[i]); // select current number
backtrack(result, tempList, nums);
tempList.remove(tempList.size() - 1); // backtrack - undo selection
}
}
}
Template to solve Heap problems:
/*s
We don't need to implement our own priority queue. We can use the built-in PriorityQueue class in Java.
Here is PriorityQueue useful methods -
- PriorityQueue(): Creates an empty priority queue.
- PriorityQueue(Comparator<? super E> comparator): Creates an empty priority queue with the specified comparator.
- isEmpty(): Checks if the priority queue is empty.
- offer(E e): Adds the specified element to the priority queue.
- poll(): Retrieves and removes the head of the queue, or returns null if the queue is empty.
- peek(): Retrieves, but does not remove, the head of the queue, or returns null if the queue is empty.
*/
import java.util.*;
public class Solution {
public ListNode mergeKLists(ListNode[] lists) {
var minHeap = new PriorityQueue<>(Comparator.comparingInt(a -> a.val));
for (ListNode node : lists) {
if (node != null) {
minHeap.offer(node);
}
}
var dummy = new ListNode(0);
ListNode curr = dummy;
while (!minHeap.isEmpty()) {
curr.next = minHeap.poll();
curr = curr.next;
if (curr.next != null) {
minHeap.offer(curr.next);
}
}
return dummy.next;
}
}
public class MedianFinder {
private PriorityQueue
public void addNum(int num) {
maxHeap.offer(num);
minHeap.offer(maxHeap.poll());
if (minHeap.size() > maxHeap.size()) {
maxHeap.offer(minHeap.poll());
}
}
public double findMedian() {
if (maxHeap.size() > minHeap.size()) {
return maxHeap.peek();
}
return (maxHeap.peek() + minHeap.peek()) / 2.0;
} } ```
Here is a simple implementation of Fenwick Tree in Java:
class FenwickTree {
private int[] tree;
private int size;
public FenwickTree(int size) {
this.size = size;
tree = new int[size + 1];
}
public void update(int index, int value) {
while (index <= size) {
tree[index] += value;
index += index & -index;
}
}
// lets understand this line with the help of binary representation
// 4 = 100
// 5 = 101
// 4 & -4 = 4
// 5 & -5 = 1
public int query(int index) {
int sum = 0;
while (index > 0) {
sum += tree[index];
index -= index & -index;
}
return sum;
}
}
class SegmentTree {
private int[] tree;
private int size;
public SegmentTree(int size) {
this.size = size;
tree = new int[4 * size];
// why 4 * size?
// this is because the segment tree is a binary tree and in the worst case, we need 4 times the size of the array to store the segment tree
}
public void build(int[] arr, int node, int start, int end) {
if (start == end) {
tree[node] = arr[start];
} else {
int mid = (start + end) / 2;
build(arr, 2 * node + 1, start, mid);
build(arr, 2 * node + 2, mid + 1, end);
tree[node] = Math.min(tree[2 * node + 1], tree[2 * node + 2]);
}
}
public void update(int index, int value, int node, int start, int end) {
if (start == end) {
tree[node] = value;
} else {
int mid = (start + end) / 2;
if (index <= mid) {
update(index, value, 2 * node + 1, start, mid);
} else {
update(index, value, 2 * node + 2, mid + 1, end);
}
tree[node] = Math.min(tree[2 * node + 1], tree[2 * node + 2]);
}
}
public int query(int L, int R, int node, int start, int end) {
if (R < start || L > end) {
return Integer.MAX_VALUE; // Return a large value for out of range
}
if (L <= start && R >= end) {
return tree[node];
}
int mid = (start + end) / 2;
return Math.min(query(L, R, 2 * node + 1, start, mid), query(L, R, 2 * node + 2, mid + 1, end));
}
}
This is not an exhaustive list of coding patterns, but it covers some of the most common ones. Also some problems can be solved using multiple patterns or combinations of patterns. The key is to practice and become familiar with these patterns so that you can recognize them when solving problems.