Chapter IV · The right Java, nothing more

Java Toolkit

Java is a big language. You don't need most of it. This is the exact subset that gets used in 99% of LeetCode solutions—paired with the problem patterns that ask for it.

Don't learn Java front-to-back and then learn DSA. Learn the small slice of Java each DSA pattern needs, when it needs it. By the time you finish this page, that slice is your working Java.

§ 1 · Essentials

Java in five minutes

Java is a statically-typed, object-oriented language. Statically-typed means every variable's type is fixed at compile time—you can't suddenly assign a String to a variable holding an int. Object-oriented means code is organised into classes. For DSA you'll rarely build elaborate class hierarchies; you'll mostly use one Solution class with a few methods, exactly like LeetCode's editor expects.

The minimum runnable program

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello, DSA");
    }
}

Variables, conditionals, loops

int x = 10;
double pi = 3.14;
boolean flag = true;
String name = "riya";

if (x > 5)        { ... }
else if (x == 5) { ... }
else              { ... }

for (int i = 0; i < 10; i++) { ... }
while (x > 0) { x--; }
for (int v : arr) { ... }        // enhanced-for

// Ternary — handy for one-liners
int max = a > b ? a : b;
§ 2 · LeetCode I/O

LeetCode method signatures

On LeetCode, you fill in a method body. You don't read input or write output. The platform calls your method and grades the return value. Internalize these shapes:

class Solution {
    // "Two Sum"
    public int[] twoSum(int[] nums, int target) { ... }

    // "Level Order Traversal"
    public List<List<Integer>> levelOrder(TreeNode root) { ... }

    // "Valid Parentheses"
    public boolean isValid(String s) { ... }
}

Local testing (when you do need stdin)

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] a = new int[n];
        for (int i = 0; i < n; i++) a[i] = sc.nextInt();
        System.out.println(Arrays.toString(a));
    }
}
§ 3 · Primitives vs wrappers

Primitives vs wrappers

Java has two kinds of types. Primitives (int, long, double, boolean, char) are raw values stored directly. Wrappers (Integer, Long, Double, Boolean, Character) are objects that wrap a primitive. The annoying rule: collections only hold objects, so a list of integers is List<Integer>, not List<int>.

PrimitiveBitsRangeWrapper
int32±2.1 × 10⁹Integer
long64±9.2 × 10¹⁸Long
double64~15 decimal digitsDouble
boolean1 (logical)true / falseBoolean
char16Unicode code unitCharacter
Overflow watch
Many DSA problems whisper "the sum may exceed 32-bit range." If the inputs are near 10⁹ and you're adding or multiplying, use long. A multiplication of two ints overflows silently—cast one to long first: (long) a * b.

Autoboxing and the == trap

Integer a = 128, b = 128;
System.out.println(a == b);          // false — reference compare
System.out.println(a.equals(b));     // true  — value compare

// For wrapper objects, always use .equals()
// For primitives, == is the value comparison.
§ 4 · Collections

The Collections cheat-sheet

This is the table. Bookmark it. Internalize the imports.

import java.util.*;       // imports ALL of the below — fine on LeetCode

// Lists
List<Integer> al = new ArrayList<>();
List<Integer> ll = new LinkedList<>();

// Sets
Set<Integer> hs = new HashSet<>();        // unordered, O(1) ops
Set<Integer> ts = new TreeSet<>();        // sorted, O(log n)
Set<Integer> lhs = new LinkedHashSet<>();   // insertion-order

// Maps
Map<String,Integer> hm  = new HashMap<>();
Map<String,Integer> tm  = new TreeMap<>();
Map<String,Integer> lhm = new LinkedHashMap<>();

// Queue / Stack / Deque — prefer ArrayDeque
Queue<Integer> q     = new ArrayDeque<>();
Deque<Integer> stack = new ArrayDeque<>();
Deque<Integer> deque = new ArrayDeque<>();

// Heap
PriorityQueue<Integer> pq    = new PriorityQueue<>();
PriorityQueue<Integer> maxPq = new PriorityQueue<>(Comparator.reverseOrder());

The methods you'll use 90% of the time

OpListSetMapQueue/Deque
addadd(x)add(x)put(k,v)offer(x)
checkcontainscontainscontainsKeycontains
fetchget(i)get(k)peek
removeremove(i)remove(x)remove(k)poll
sizesize()size()size()size()

Iterating a map — the patterns

// Keys only
for (String k : map.keySet()) { ... }

// Values only
for (int v : map.values()) { ... }

// Both — preferred
for (Map.Entry<String, Integer> e : map.entrySet()) {
    String k = e.getKey();
    int    v = e.getValue();
}

Converting between forms

// int[] → List<Integer>
List<Integer> list = Arrays.stream(arr).boxed().collect(Collectors.toList());

// List<Integer> → int[]
int[] back = list.stream().mapToInt(Integer::intValue).toArray();

// Array → List view (FIXED-SIZE, do not call add/remove)
List<String> view = Arrays.asList("a", "b");

// Set → List
List<Integer> fromSet = new ArrayList<>(set);

// String → char[]
char[] chars = s.toCharArray();

// char[] → String
String back = new String(chars);
§ 5 · Comparator

Comparator & custom sorting

A Comparator is just a tiny function: "given a and b, which one comes first?" Return a negative number to put a before b, positive for after, zero for equal. Modern Java lets you write this as a one-line lambda. This is the single most useful Java idiom for DSA—every interview problem with custom ordering uses it.

Basics

// Ascending — the default
Arrays.sort(arr);

// Descending — Integer[] not int[]
Arrays.sort(boxedArr, (a, b) -> b - a);

// Safer for big numbers (avoids overflow)
Arrays.sort(boxedArr, (a, b) -> Integer.compare(b, a));

// Sort objects by field
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);

// Multi-key — by first, ties broken by second
Arrays.sort(intervals, (a, b) ->
    a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]);

Comparator.comparing — the readable form

// Sort meetings by start, then by end
Arrays.sort(meetings, Comparator
    .comparingInt((int[] m) -> m[0])
    .thenComparingInt(m -> m[1]));

// Reverse easily
list.sort(Comparator.reverseOrder());

// Custom class
people.sort(Comparator.comparing(Person::getAge).reversed());
The b - a overflow trap
(a, b) -> b - a looks elegant but breaks when b is very negative and a is very positive — the subtraction overflows. For competitive-style problems with wide ranges, always use Integer.compare(b, a) or Long.compare(...).
§ 6 · Strings & StringBuilder

Strings & StringBuilder

Strings in Java are immutable. Every "edit" creates a new String. Concatenating in a loop is therefore O(n²) — each += copies the whole string. Use StringBuilder for any string built character-by-character. This single change can drop "Time Limit Exceeded" to "Accepted."

String — the methods that matter

String s = "hello world";

s.length();                  // 11
s.charAt(0);                // 'h'
s.substring(0, 5);          // "hello" — [start, end)
s.indexOf("world");          // 6, or -1
s.contains("ell");           // true
s.startsWith("he");          // true
s.endsWith("ld");            // true
s.toLowerCase();
s.toUpperCase();
s.trim();                    // strip leading/trailing whitespace
s.replace('l', 'L');        // "heLLo worLd"
s.split(" ");                // ["hello", "world"]
s.toCharArray();
s.equals("hello world");     // content equality
String.valueOf(123);          // int → "123"
Integer.parseInt("123");     // "123" → 123

StringBuilder — the workhorse

StringBuilder sb = new StringBuilder();

sb.append("hi");
sb.append(42);
sb.append('!');
sb.insert(0, ">>>");              // insert at index
sb.deleteCharAt(1);
sb.reverse();                       // in-place reverse
sb.length();
sb.charAt(2);
sb.setCharAt(2, 'X');
String result = sb.toString();
When does StringBuilder matter?
Any time you're accumulating characters in a loop — building a result string in backtracking, joining tokens, constructing a reversed string. For one-off concatenations of two or three strings, plain + is fine; the compiler optimises it.
§ 7 · Generics

Generics

Generics are why you write List<Integer> instead of just List. The angle brackets tell Java what type lives inside the collection, so you get compile-time type-checking and don't have to cast on every get. For DSA, you'll use them mostly to declare collections; you'll rarely write your own generic class.

// Without generics — gross, error-prone
List names = new ArrayList();
names.add("Riya");
String s = (String) names.get(0);    // manual cast required

// With generics — clean and type-safe
List<String> names2 = new ArrayList<>();
names2.add("Riya");
String s2 = names2.get(0);          // no cast

// The diamond <> — infers the type
Map<String, List<Integer>> index = new HashMap<>();

A simple generic class

class Pair<A, B> {
    A first;
    B second;
    Pair(A a, B b) { first = a; second = b; }
}

Pair<String, Integer> p = new Pair<>("score", 99);
§ 8 · Lambdas & functional

Lambdas & functional bits

A lambda is an anonymous function — a short way to pass behaviour around like data. You've already used them in Comparators. They show up wherever Java expects a single-method interface (called a "functional interface"), and they make your DSA code dramatically shorter.

// Single arg, no parens needed
Function<Integer, Integer> square = x -> x * x;
square.apply(5);   // 25

// Two args
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;

// Predicate — returns boolean
Predicate<Integer> isEven = n -> n % 2 == 0;

// Consumer — no return
Consumer<String> print = s -> System.out.println(s);
list.forEach(print);
list.forEach(System.out::println);   // method reference

Streams — when they help

Streams are a fluent API for transforming collections. Great for one-line transformations, sometimes slower than a plain loop on huge inputs. Use sparingly in DSA—a clear loop usually wins.

// Sum of even squares
int total = Arrays.stream(nums)
    .filter(n -> n % 2 == 0)
    .map(n -> n * n)
    .sum();

// Distinct sorted list
List<Integer> uniq = list.stream()
    .distinct()
    .sorted()
    .collect(Collectors.toList());
§ 9 · Math & numeric edges

Math & numeric edges

Math.max(a, b);
Math.min(a, b);
Math.abs(x);
Math.pow(2, 10);          // 1024.0 (double — cast if needed)
Math.sqrt(16);             // 4.0
Math.floor(3.7);            // 3.0
Math.ceil(3.2);             // 4.0
Math.floorDiv(-7, 2);       // -4   (vs. -7/2 = -3 in Java)
Math.floorMod(-7, 3);       // 2    (always non-negative for + mod)

// Limits
Integer.MAX_VALUE;            // 2,147,483,647
Integer.MIN_VALUE;
Long.MAX_VALUE;
Negative modulo
In Java, -7 % 3 is -1, not 2. For hash-style "wrap to positive" math, use Math.floorMod(-7, 3) or ((x % m) + m) % m. Many graph and grid problems quietly need this.
§ 10 · Pitfalls

Top 10 Java pitfalls in DSA

  1. == on Strings / wrappers compares references, not content. Always use .equals().
  2. Array length is arr.length; String length is s.length(); Collection size is list.size(). Three different forms—memorise them.
  3. list.remove(int) ambiguity. On a List<Integer>, remove(2) removes index 2. To remove the value 2, write remove(Integer.valueOf(2)).
  4. Integer overflow. When inputs near 10⁹ and you're adding or multiplying, switch to long: long product = (long) a * b;.
  5. b - a in a Comparator overflows for very large/small values. Prefer Integer.compare(b, a).
  6. Modifying a collection while iterating throws ConcurrentModificationException. Use an Iterator's remove() or collect-then-remove.
  7. String concatenation in a loop is O(n²). Use StringBuilder.
  8. Arrays.asList(intArr) returns a List<int[]> of size 1, not a List<Integer>. Use Arrays.stream(arr).boxed().toList().
  9. Integer division. 5 / 2 is 2, not 2.5. Cast to double for fractional results: (double) 5 / 2.
  10. Forgetting to snapshot in backtracking. Add new ArrayList<>(current) to the result list, not current itself—or every entry will be the same mutated list.
§ 11 · Snippet vault

The snippet vault

Six copy-paste-ready blocks you'll reach for in interview after interview.

Frequency map of a String

Map<Character, Integer> freq = new HashMap<>();
for (char c : s.toCharArray())
    freq.merge(c, 1, Integer::sum);

Top-K with a min-heap

PriorityQueue<Integer> pq = new PriorityQueue<>();
for (int n : nums) {
    pq.offer(n);
    if (pq.size() > k) pq.poll();
}
// pq now holds the k largest values; pq.peek() is the kth-largest

Grid 4-direction BFS

int[][] D = {{-1,0},{1,0},{0,-1},{0,1}};
for (int[] d : D) {
    int nr = r + d[0], nc = c + d[1];
    if (nr < 0 || nr >= R || nc < 0 || nc >= C) continue;
    // process (nr, nc)
}

Reverse a linked list

ListNode prev = null, curr = head;
while (curr != null) {
    ListNode next = curr.next;
    curr.next = prev;
    prev = curr;
    curr = next;
}
return prev;

Two-pointer template (sorted array, pair sum)

int l = 0, r = nums.length - 1;
while (l < r) {
    int sum = nums[l] + nums[r];
    if (sum == target) return new int[]{l, r};
    if (sum < target) l++; else r--;
}

Tree DFS skeleton

int dfs(TreeNode node) {
    if (node == null) return 0;            // base case
    int left  = dfs(node.left);
    int right = dfs(node.right);
    // combine with node.val
    return Math.max(left, right) + 1;
}

You now have complexity, structures, patterns, and Java. The rest is hours at the keyboard.

Back to the index