Core Algorithmic Techniques in Java for Competitive Programming

Sorting, searching, dynamic programing, and graph algorithms form the backbone of competitive programming. This article presents a curated list of such patterns, each accompanied by a concise Java implementation. The examples draw inspiration from the ACwing problem set, covering topics from basic sorting to advanced combinatorial mathematics.

Basic Algorithms

Sorting

Quick Sort (mid-point pivot)

The array is partitioned around a pivot chosen from the middle index, and then recursively sorted.

void quickSort(int[] arr, int l, int r) {
  if (l >= r) return;
  int pivot = arr[(l + r) >> 1];
  int i = l - 1, j = r + 1;
  while (i < j) {
    do i++; while (arr[i] < pivot);
    do j--; while (arr[j] > pivot);
    if (i < j) swap(arr, i, j);
  }
  quickSort(arr, l, j);
  quickSort(arr, j + 1, r);
}
void swap(int[] arr, int x, int y) { int t = arr[x]; arr[x] = arr[y]; arr[y] = t; }

Merge Sort and Inversion Count

Merge sort can be extended to count the number of inversions.

long invCount(int[] arr, int l, int r, int[] tmp) {
  if (l >= r) return 0;
  int m = (l + r) >> 1;
  long left = invCount(arr, l, m, tmp);
  long right = invCount(arr, m + 1, r, tmp);
  return left + right + merge(arr, l, m, r, tmp);
}
long merge(int[] arr, int l, int m, int r, int[] tmp) {
  int i = l, j = m + 1, k = l;
  long inv = 0;
  while (i <= m && j <= r) {
    if (arr[i] <= arr[j]) tmp[k++] = arr[i++];
    else { tmp[k++] = arr[j++]; inv += (m - i + 1); }
  }
  while (i <= m) tmp[k++] = arr[i++];
  while (j <= r) tmp[k++] = arr[j++];
  for (i = l; i <= r; i++) arr[i] = tmp[i];
  return inv;
}

Binary Search

Integer boundaries and floating-point roots both rely on a precise two-pointer approach.

int lowerBound(int[] arr, int target) {
  int l = 0, r = arr.length - 1;
  while (l < r) {
    int m = (l + r) >> 1;
    if (arr[m] >= target) r = m;
    else l = m + 1;
  }
  return arr[l] == target ? l : -1;
}
double cubeRoot(double n) {
  double l = -100.0, r = 100.0;
  while (r - l > 1e-8) {
    double m = (l + r) / 2;
    if (m * m * m >= n) r = m;
    else l = m;
  }
  return l;
}

Prefix Sum and Difference Arrays

Efficient range updates and queries are achieved through prefix sums in 1D and 2D.

// 1D prefix
int[] pref = new int[n + 1];
for (int i = 0; i < n; i++) pref[i + 1] = pref[i] + arr[i];
int rangeSum = pref[r] - pref[l - 1];

// 2D prefix
int[][] pref2 = new int[n + 1][m + 1];
for (int i = 1; i <= n; i++)
  for (int j = 1; j <= m; j++)
    pref2[i][j] = pref2[i - 1][j] + pref2[i][j - 1] - pref2[i - 1][j - 1] + mat[i][j];
int sum = pref2[x2][y2] - pref2[x1 - 1][y2] - pref2[x2][y1 - 1] + pref2[x1 - 1][y1 - 1];

// Difference array for range add
int[] diff = new int[n + 2];
void addRange(int l, int r, int v) { diff[l] += v; diff[r + 1] -= v; }
for (int i = 1; i <= n; i++) arr[i] = diff[i] + arr[i - 1];

Two Pointers

// Longest substring without repeating characters
int[] cnt = new int[1000000];
int maxLen = 0;
for (int i = 0, j = 0; i < arr.length; i++) {
  cnt[arr[i]]++;
  while (j <= i && cnt[arr[i]] > 1) cnt[arr[j++]]--;
  maxLen = Math.max(maxLen, i - j + 1);
}

Bit Manipulation

int numberOfOnes(int x) {
  int c = 0;
  while (x > 0) { x -= x & -x; c++; }
  return c;
}

Coordinate Compression

Map sparse values to consecutive indices using binary search or a hash map.

// After sorting unique sorted array 'pos', map original coordinate to compressed index
int findCompressedIndex(int[] pos, int len, int val) {
  int l = 0, r = len - 1;
  while (l < r) {
    int m = (l + r) >> 1;
    if (pos[m] >= val) r = m;
    else l = m + 1;
  }
  return r + 1; // 1-based
}

Interval Merging

int[][] intervals; // sorted by left endpoint
List<int[]> merged = new ArrayList<>();
int st = intervals[0][0], ed = intervals[0][1];
for (int i = 1; i < intervals.length; i++) {
  if (intervals[i][0] > ed) {
    merged.add(new int[]{st, ed});
    st = intervals[i][0];
    ed = intervals[i][1];
  } else ed = Math.max(ed, intervals[i][1]);
}
merged.add(new int[]{st, ed});

Data Structures

Singly and Doubly Linked List (array simulation)

class SinglyList {
  int[] e = new int[100010], ne = new int[100010];
  int idx = 1; // head is at index 0
  void insertHead(int v) { e[idx] = v; ne[idx] = ne[0]; ne[0] = idx++; }
  void insertAfter(int k, int v) { e[idx] = v; ne[idx] = ne[k]; ne[k] = idx++; }
  void remove(int k) { ne[k] = ne[ne[k]]; }
}

Stack (array simulation)

int[] stk = new int[100010];
int tp = 0;
void push(int v) { stk[tp++] = v; }
int pop() { return stk[--tp]; }
int peek() { return stk[tp - 1]; }
boolean isEmpty() { return tp == 0; }

Monotonic Stack and Queue

Find the first smaller element on the left:

int[] stk = new int[n];
int tp = 0;
for (int i = 0; i < n; i++) {
  while (tp > 0 && arr[stk[tp - 1]] >= arr[i]) tp--;
  int leftSmaller = tp == 0 ? -1 : arr[stk[tp - 1]];
  stk[tp++] = i;
}

Sliding window maximum:

int[] q = new int[n];
int head = -1, tail = 0;
for (int i = 0; i < n; i++) {
  if (tail <= head && q[tail] <= i - k) tail++;
  while (tail <= head && arr[q[head]] <= arr[i]) head--;
  q[++head] = i;
  if (i >= k - 1) res[i - k + 1] = arr[q[tail]];
}

KMP Pattern Matching

int[] buildNext(String p) {
  int m = p.length();
  int[] next = new int[m + 1];
  for (int i = 2, j = 0; i <= m; i++) {
    while (j > 0 && p.charAt(j) != p.charAt(i - 1)) j = next[j];
    if (p.charAt(j) == p.charAt(i - 1)) j++;
    next[i] = j;
  }
  return next;
}
List<Integer> kmp(String s, String p) {
  List<Integer> ans = new ArrayList<>();
  int[] next = buildNext(p);
  int n = s.length(), m = p.length();
  for (int i = 1, j = 0; i <= n; i++) {
    while (j > 0 && p.charAt(j) != s.charAt(i - 1)) j = next[j];
    if (p.charAt(j) == s.charAt(i - 1)) j++;
    if (j == m) { ans.add(i - m); j = next[j]; }
  }
  return ans;
}

Trie

class Trie {
  int[][] son = new int[100010][26];
  int[] cnt = new int[100010];
  int idx = 0;
  void insert(String s) {
    int p = 0;
    for (char c : s.toCharArray()) {
      int u = c - 'a';
      if (son[p][u] == 0) son[p][u] = ++idx;
      p = son[p][u];
    }
    cnt[p]++;
  }
  int query(String s) {
    int p = 0;
    for (char c : s.toCharArray()) {
      int u = c - 'a';
      if (son[p][u] == 0) return 0;
      p = son[p][u];
    }
    return cnt[p];
  }
}

Disojint Set Union (Union-Find)

class DSU {
  int[] parent, size;
  DSU(int n) {
    parent = new int[n]; size = new int[n];
    for (int i = 0; i < n; i++) { parent[i] = i; size[i] = 1; }
  }
  int find(int x) {
    int root = x;
    while (root != parent[root]) root = parent[root];
    while (x != parent[x]) { int nxt = parent[x]; parent[x] = root; x = nxt; }
    return root;
  }
  void union(int a, int b) {
    int ra = find(a), rb = find(b);
    if (ra != rb) { parent[rb] = ra; size[ra] += size[rb]; }
  }
  int getSize(int x) { return size[find(x)]; }
}

Heap (array simulation)

int[] heap = new int[100010];
int sz = 0;
void down(int u) {
  int t = u;
  if (u * 2 <= sz && heap[u * 2] < heap[t]) t = u * 2;
  if (u * 2 + 1 <= sz && heap[u * 2 + 1] < heap[t]) t = u * 2 + 1;
  if (t != u) { swap(u, t); down(t); }
}
void build() { for (int i = sz / 2; i >= 1; i--) down(i); }
int pop() {
  int v = heap[1];
  heap[1] = heap[sz--];
  down(1);
  return v;
}

String Hashing

int P = 131;
long[] h, p;
void init(char[] s) {
  int n = s.length;
  h = new long[n + 1]; p = new long[n + 1];
  p[0] = 1;
  for (int i = 1; i <= n; i++) {
    p[i] = p[i - 1] * P;
    h[i] = h[i - 1] * P + s[i - 1];
  }
}
long getHash(int l, int r) { // 1-indexed
  return h[r] - h[l - 1] * p[r - l + 1];
}

Graph and Search

DFS and BFS

Classic DFS for permutations:

boolean[] used = new boolean[n];
int[] cur = new int[n];
void dfs(int pos)
  if (pos == n) { process(cur); return; }
  for (int i = 1; i <= n; i++) {
    if (!used[i]) { used[i] = true; cur[pos] = i; dfs(pos + 1); used[i] = false; }
  }
}

BFS for shortest path in a grid:

int[][] dir = {{1,0},{-1,0},{0,1},{0,-1}};
Queue<int[]> q = new ArrayDeque<>();
boolean[][] vis = new boolean[n][m];
q.offer(new int[]{0,0,0}); vis[0][0] = true;
while (!q.isEmpty()) {
  int[] cur = q.poll();
  int r = cur[0], c = cur[1], step = cur[2];
  if (r == n - 1 && c == m - 1) { ans = step; break; }
  for (int[] d : dir) {
    int nr = r + d[0], nc = c + d[1];
    if (nr >= 0 && nr < n && nc >= 0 && nc < m && !vis[nr][nc] && grid[nr][nc] == 0) {
      vis[nr][nc] = true;
      q.offer(new int[]{nr, nc, step + 1});
    }
  }
}

Topological Sort

int[] indeg = new int[n];
List<Integer>[] g = new ArrayList[n];
Queue<Integer> q = new ArrayDeque<>();
for (int i = 0; i < n; i++) if (indeg[i] == 0) q.offer(i);
List<Integer> order = new ArrayList<>();
while (!q.isEmpty()) {
  int u = q.poll(); order.add(u);
  for (int v : g[u]) if (--indeg[v] == 0) q.offer(v);
}
if (order.size() != n) // cycle exists

Shortest Paths

Dijkstra (naive O(n²) for dense graphs)

int[] dist = new int[n];
boolean[] vis = new boolean[n];
Arrays.fill(dist, INF); dist[src] = 0;
for (int i = 0; i < n; i++) {
  int t = -1;
  for (int j = 0; j < n; j++) if (!vis[j] && (t == -1 || dist[j] < dist[t])) t = j;
  if (dist[t] == INF) break;
  vis[t] = true;
  for (int j = 0; j < n; j++) if (g[t][j] != INF) dist[j] = Math.min(dist[j], dist[t] + g[t][j]);
}

Dijkstra with heap (O(m log n))

PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
int[] dist = new int[n]; Arrays.fill(dist, INF);
dist[src] = 0; pq.offer(new int[]{0, src});
while (!pq.isEmpty()) {
  int[] top = pq.poll();
  int d = top[0], u = top[1];
  if (d > dist[u]) continue;
  for (int[] e : adj[u]) {
    int v = e[0], w = e[1];
    if (dist[v] > d + w) {
      dist[v] = d + w;
      pq.offer(new int[]{dist[v], v});
    }
  }
}

Bellman-Ford (k-edges restriction)

int[] dist = new int[n]; Arrays.fill(dist, INF); dist[src] = 0;
for (int i = 0; i < k; i++) {
  int[] backup = dist.clone();
  for (int[] e : edges) {
    int a = e[0], b = e[1], w = e[2];
    if (backup[a] != INF) dist[b] = Math.min(dist[b], backup[a] + w);
  }
}

SPFA

int[] dist = new int[n]; boolean[] inQ = new boolean[n];
Arrays.fill(dist, INF); dist[src] = 0;
Queue<Integer> q = new ArrayDeque<>(); q.offer(src); inQ[src] = true;
while (!q.isEmpty()) {
  int u = q.poll(); inQ[u] = false;
  for (int[] e : adj[u]) {
    int v = e[0], w = e[1];
    if (dist[v] > dist[u] + w) {
      dist[v] = dist[u] + w;
      if (!inQ[v]) { q.offer(v); inQ[v] = true; }
    }
  }
}

Floyd-Warshall (all-pair)

int[][] d = new int[n][n]; // init with adjacency matrix, diagonal 0, missing edges INF
for (int k = 0; k < n; k++)
  for (int i = 0; i < n; i++)
    for (int j = 0; j < n; j++)
      if (d[i][k] != INF && d[k][j] != INF) d[i][j] = Math.min(d[i][j], d[i][k] + d[k][j]);

Minimum Spanning Tree

Prim (O(n²)):

int[] dist = new int[n]; Arrays.fill(dist, INF); dist[0] = 0;
boolean[] inTree = new boolean[n];
int total = 0;
for (int i = 0; i < n; i++) {
  int t = -1;
  for (int j = 0; j < n; j++) if (!inTree[j] && (t == -1 || dist[j] < dist[t])) t = j;
  if (dist[t] == INF) { total = INF; break; } // no MST
  inTree[t] = true; total += dist[t];
  for (int j = 0; j < n; j++) if (!inTree[j] && g[t][j] != INF) dist[j] = Math.min(dist[j], g[t][j]);
}

Kruskal:

DSU dsu = new DSU(n);
int res = 0, cnt = 0;
Arrays.sort(edges, Comparator.comparingInt(e -> e[2]));
for (int[] e : edges) {
  int a = e[0], b = e[1], w = e[2];
  if (dsu.find(a) != dsu.find(b)) {
    dsu.union(a, b); res += w; cnt++;
  }
}
if (cnt != n - 1) // no MST

Bipartite Graph Matching (Hungarian Algorithm)

int[] match = new int[m]; // boy -> girl mapping
Arrays.fill(match, -1);
boolean[] vis = new boolean[m];
int ans = 0;
for (int b = 0; b < n; b++) {
  Arrays.fill(vis, false);
  if (dfs(b, match, vis, adj)) ans++;
}
boolean dfs(int b, int[] match, boolean[] vis, List<Integer>[] adj) {
  for (int g : adj[b]) {
    if (vis[g]) continue;
    vis[g] = true;
    if (match[g] == -1 || dfs(match[g], match, vis, adj)) {
      match[g] = b;
      return true;
    }
  }
  return false;
}

Mathematics

Primes

Trial division for primality:

boolean isPrime(int x) {
  if (x < 2) return false;
  for (int i = 2; i * i <= x; i++) if (x % i == 0) return false;
  return true;
}

Sieve (linear) to count primes up to n:

List<Integer> primes = new ArrayList<>();
boolean[] comp = new boolean[n + 1];
for (int i = 2; i <= n; i++) {
  if (!comp[i]) primes.add(i);
  for (int j = 0; primes.get(j) * i <= n; j++) {
    comp[primes.get(j) * i] = true;
    if (i % primes.get(j) == 0) break;
  }
}

Divisors and GCD

List<Integer> divisors(int n) {
  List<Integer> res = new ArrayList<>();
  for (int i = 1; i * i <= n; i++) {
    if (n % i == 0) {
      res.add(i);
      if (i != n / i) res.add(n / i);
    }
  }
  return res;
}
int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }

Euler's Totient Function

int phi(int n) {
  int res = n;
  for (int i = 2; i * i <= n; i++) {
    if (n % i == 0) {
      res = res / i * (i - 1);
      while (n % i == 0) n /= i;
    }
  }
  if (n > 1) res = res / n * (n - 1);
  return res;
}

Fast Exponentiation and Modular Inverse

long powMod(long a, long b, long m) {
  long res = 1;
  while (b > 0) {
    if ((b & 1) == 1) res = res * a % m;
    a = a * a % m;
    b >>= 1;
  }
  return res;
}
long modInv(long a, long p) { return powMod(a, p - 2, p); } // p prime

Extended Euclidean Algorithm

int[] exgcd(int a, int b) {
  if (b == 0) return new int[]{1, 0, a};
  int[] res = exgcd(b, a % b);
  int x = res[0], y = res[1], g = res[2];
  return new int[]{y, x - a / b * y, g};
}

Combinatorics (Binomial Coefficients)

long[][] C = new long[2001][2001];
for (int i = 0; i <= 2000; i++) {
  for (int j = 0; j <= i; j++) {
    C[i][j] = (j == 0) ? 1 : (C[i - 1][j] + C[i - 1][j - 1]) % MOD;
  }
}

Dynamic Programming

Knapsack Models

0-1 knapsack:

int[] dp = new int[V + 1];
for (int i = 0; i < N; i++)
  for (int v = V; v >= w[i]; v--)
    dp[v] = Math.max(dp[v], dp[v - w[i]] + v[i]);

Complete knapsack:

for (int i = 0; i < N; i++)
  for (int v = w[i]; v <= V; v++)
    dp[v] = Math.max(dp[v], dp[v - w[i]] + v[i]);

Group knapsack:

for (int i = 0; i < N; i++) // groups
  for (int v = V; v >= 0; v--)
    for (int k = 0; k < group[i].length; k++)
      if (v >= gvol[i][k]) dp[v] = Math.max(dp[v], dp[v - gvol[i][k]] + gval[i][k]);

Linear and Interval DP

Longest Increasing Subsequence:

int[] dp = new int[n]; Arrays.fill(dp, 1);
int ans = 0;
for (int i = 0; i < n; i++) {
  for (int j = 0; j < i; j++)
    if (nums[j] < nums[i]) dp[i] = Math.max(dp[i], dp[j] + 1);
  ans = Math.max(ans, dp[i]);
}

Stone merging (interval DP):

int[][] dp = new int[n + 1][n + 1];
for (int len = 2; len <= n; len++)
  for (int l = 1; l + len - 1 <= n; l++) {
    int r = l + len - 1;
    dp[l][r] = INF;
    for (int k = l; k < r; k++)
      dp[l][r] = Math.min(dp[l][r], dp[l][k] + dp[k + 1][r] + pref[r] - pref[l - 1]);
  }

State Compression DP

Hamilton shortest path:

int[][] dp = new int[n][1 << n];
for (int[] row : dp) Arrays.fill(row, INF);
dp[0][1] = 0;
for (int mask = 0; mask < (1 << n); mask++)
  for (int v = 0; v < n; v++)
    if ((mask >> v & 1) == 1)
      for (int u = 0; u < n; u++)
        if ((mask >> u & 1) == 1)
          dp[v][mask] = Math.min(dp[v][mask], dp[u][mask ^ (1 << v)] + w[u][v]);

Tree DP (party without boss)

int[][] dp = new int[n + 1][2];
void dfs(int u, int parent) {
  dp[u][1] = happy[u];
  for (int v : tree[u]) {
    if (v == parent) continue;
    dfs(v, u);
    dp[u][0] += Math.max(dp[v][0], dp[v][1]);
    dp[u][1] += dp[v][0];
  }
}

Memorized Search (skiing)

int[][] mem = new int[r][c];
for (int[] row : mem) Arrays.fill(row, -1);
int dfs(int x, int y) {
  if (mem[x][y] != -1) return mem[x][y];
  int best = 1;
  for (int[] d : dir) {
    int nx = x + d[0], ny = y + d[1];
    if (inRange(nx, ny) && height[nx][ny] < height[x][y])
      best = Math.max(best, dfs(nx, ny) + 1);
  }
  return mem[x][y] = best;
}

Greedy Strategies

Interval Problems

Maximum number of non-overlapping intervals:

Arrays.sort(intervals, Comparator.comparingInt(a -> a[1]));
int cnt = 0, lastEnd = Integer.MIN_VALUE;
for (int[] inv : intervals) {
  if (inv[0] > lastEnd) { cnt++; lastEnd = inv[1]; }
}

Minimum meeting rooms (interval partitioning):

Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]));
PriorityQueue<Integer> pq = new PriorityQueue<>();
for (int[] inv : intervals) {
  if (!pq.isEmpty() && pq.peek() < inv[0]) pq.poll();
  pq.offer(inv[1]);
}

Minimum intervals to cover a range:

Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]));
int i = 0, ans = 0, curStart = targetStart;
while (i < n && curStart < targetEnd) {
  int maxRight = curStart;
  while (i < n && intervals[i][0] <= curStart) {
    maxRight = Math.max(maxRight, intervals[i][1]);
    i++;
  }
  if (maxRight == curStart) break; // cannot cover
  ans++; curStart = maxRight;
}

Huffman Coding

PriorityQueue<Long> pq = new PriorityQueue<>();
for (long v : arr) pq.offer(v);
long cost = 0;
while (pq.size() > 1) {
  long a = pq.poll(), b = pq.poll();
  cost += a + b;
  pq.offer(a + b);
}

Load Balancing (Acrobatic Cows)

Sort by weight + strength ascending:

Arrays.sort(cows, Comparator.comparingLong(c -> c[0] + c[1]));
long totalW = 0, maxRisk = Long.MIN_VALUE;
for (long[] c : cows) {
  maxRisk = Math.max(maxRisk, totalW - c[1]);
  totalW += c[0];
}

Warehouse Location (Absolute Value Inequality)

Arrays.sort(positions);
long median = positions[positions.length / 2];
long dist = 0;
for (long p : positions) dist += Math.abs(p - median);

Job Scheduling (Sorting Inequality)

Arrays.sort(processingTimes);
long totalWait = 0;
for (int i = 0; i < n; i++) totalWait += processingTimes[i] * (n - i - 1);

Tags: java ACwing algorithms Data Structures Competitive Programming

Posted on Mon, 15 Jun 2026 17:07:00 +0000 by zhaohongli