Chapter 2: Java Prerequisites for DSA
2.05Beginner10 topics16 min

Core library utilities

The standard library pieces used constantly in algorithm code: Math, Arrays.sort and binarySearch, Collections.sort, StringBuilder, StringJoiner, Character, BigInteger, BitSet and Random, with their pitfalls.

Learning goals

  • Use the right Math and Arrays methods
  • Understand how sorting and binary search behave in Java
  • Build strings efficiently
  • Know when BigInteger, BitSet and seeded Random help

Prerequisites

Java collections framework
2.05.1

Math

Math offers abs, min, max, pow, sqrt, floorDiv, floorMod, log, ceil, floor, and the exact variants addExact, multiplyExact and toIntExact that throw on overflow. Math.pow returns a double and loses precision above 2⁵³, so compute integer powers with a loop or fast exponentiation.

Math.abs(Integer.MIN_VALUE) is still negative (see Numeric correctness).

Java
1int m = Math.max(3, 9);
2int fm = Math.floorMod(-7, 3); // 2
3int safe = Math.addExact(1, 2);
4// Math.multiplyExact(100_000, 100_000) throws ArithmeticException (int overflow)
5int lg = 31 - Integer.numberOfLeadingZeros(37); // floor(log2(37)) = 5
6long p = 1; for (int i = 0; i < 10; i++) p *= 3; // 3^10 exactly: 59049
2.05.2

Arrays.sort

Arrays.sort(int[]) uses a dual-pivot quicksort (not stable, which is irrelevant for primitives). Arrays.sort(T[]) and Collections.sort use TimSort, which is stable and O(n log n) in the worst case. You can sort a range with Arrays.sort(a, from, to).

Primitive arrays cannot take a comparator. To sort int[] in descending order, sort ascending and reverse, or use boxed Integer[] with Collections.reverseOrder().

Java
1int[] a = {5, 2, 9, 1};
2Arrays.sort(a); // [1, 2, 5, 9]
3Integer[] b = {5, 2, 9, 1};
4Arrays.sort(b, Collections.reverseOrder()); // [9, 5, 2, 1]
5int[][] pairs = {{3, 1}, {1, 2}, {3, 0}};
6Arrays.sort(pairs, (x, y) -> x[0] != y[0] ? Integer.compare(x[0], y[0]) : Integer.compare(x[1], y[1]));
2.05.3

Arrays.binarySearch

Arrays.binarySearch(sorted, key) returns the index when found; when not found it returns -(insertionPoint) - 1, a negative number that encodes where the key would be inserted. With duplicates it may return any matching index.

Decode a miss with -(r + 1). The array must be sorted, otherwise the result is undefined.

Java
1int[] a = {10, 20, 30, 40};
2int hit = Arrays.binarySearch(a, 30); // 2
3int miss = Arrays.binarySearch(a, 25); // -3 (insertion point 2)
4int insertAt = -(miss + 1); // 2
2.05.4

Collections.sort

Collections.sort(list) or list.sort(cmp) sorts a List in place using a stable merge sort. Stability lets you sort by several keys in succession: sort by the secondary key first, then by the primary key.

Comparators must be consistent: if compare(a, b) < 0 then compare(b, a) > 0. An inconsistent comparator can throw “Comparison method violates its general contract”.

Java
1record Emp(String dept, int pay) {}
2List<Emp> es = new ArrayList<>(List.of(new Emp("B", 5), new Emp("A", 7), new Emp("B", 3), new Emp("A", 7)));
3es.sort(Comparator.comparing(Emp::dept).thenComparingInt(Emp::pay).reversed());
4// deterministic order using both keys
2.05.5

StringBuilder

String is immutable, so s += x in a loop copies the whole string each time and costs O(n²) overall. StringBuilder appends in amortised O(1) and is the right tool for building output.

Useful methods: append, insert, reverse, setCharAt, deleteCharAt, length, toString.

Java
1StringBuilder sb = new StringBuilder();
2for (int i = 0; i < 5; i++) sb.append(i).append(' ');
3String out = sb.toString().trim(); // "0 1 2 3 4"
4String rev = new StringBuilder("abc").reverse().toString(); // "cba"
5boolean pal = new StringBuilder("level").reverse().toString().equals("level");
2.05.6

StringJoiner

StringJoiner and String.join insert a delimiter between items, and avoid the trailing-separator problem. Collectors.joining does the same for streams.

For output of many lines, one StringBuilder plus a single print is faster than repeated System.out.println.

Java
1StringJoiner sj = new StringJoiner(", ", "[", "]");
2sj.add("a").add("b").add("c");
3String s = sj.toString(); // "[a, b, c]"
4String t = String.join("-", List.of("x", "y")); // "x-y"
5String u = IntStream.of(1, 2, 3).mapToObj(String::valueOf).collect(Collectors.joining(" "));
2.05.7

Character

Character helpers: isDigit, isLetter, isLetterOrDigit, isUpperCase, toLowerCase, getNumericValue. They work on Unicode characters; for plain ASCII checks you can compare ranges.

Convert a digit char to its value with c - '0', and a letter to an index with c - 'a'.

Java
1boolean d = Character.isDigit('7'); // true
2char lo = Character.toLowerCase('Q'); // 'q'
3int v = Character.getNumericValue('9'); // 9
4int pos = Character.toLowerCase('E') - 'a'; // 4
2.05.8

BigInteger

BigInteger holds arbitrarily large integers and provides add, multiply, pow, mod, modPow, modInverse, gcd and isProbablePrime. It is immutable and much slower than long, so use it only when a value cannot fit in 64 bits (large factorials, exact Fibonacci numbers).

Create values with BigInteger.valueOf(long) or from a decimal string.

Java
1BigInteger f = BigInteger.ONE;
2for (int i = 2; i <= 25; i++) f = f.multiply(BigInteger.valueOf(i));
3// 25! = 15511210043330985984000000 (exceeds long)
4BigInteger r = BigInteger.TWO.modPow(BigInteger.valueOf(100), BigInteger.valueOf(1_000_000_007));
5boolean prime = BigInteger.valueOf(97).isProbablePrime(20);
2.05.9

BitSet

BitSet stores one bit per index (about n/8 bytes) and offers set, get, clear, flip, cardinality, nextSetBit and bulk and, or, xor. It is the compact choice for sieves and visited flags over very large ranges.

A boolean[] uses one byte per entry, so BitSet is about 8 times smaller but a little slower per access.

Java
1int n = 50;
2BitSet composite = new BitSet(n + 1);
3for (int i = 2; (long) i * i <= n; i++)
4 if (!composite.get(i)) for (int j = i * i; j <= n; j += i) composite.set(j);
5int primes = 0;
6for (int i = 2; i <= n; i++) if (!composite.get(i)) primes++; // 15
2.05.10

Random

new Random(seed) produces the same sequence for the same seed, which makes randomised tests reproducible. nextInt(bound) returns 0 to bound-1; nextInt(lo, hi) is available from Java 17. Shuffle with Collections.shuffle(list, rnd).

For many threads use ThreadLocalRandom. Do not use Random for security.

Java
1Random rnd = new Random(12345);
2int roll = rnd.nextInt(6) + 1; // 1..6
3int[] data = rnd.ints(5, 0, 100).toArray(); // 5 values in [0, 100)
4List<Integer> xs = new ArrayList<>(List.of(1, 2, 3, 4));
5Collections.shuffle(xs, new Random(7));

Practice ladder

Now try it yourself

Easy

  • Build the string "1,2,3,4,5" using StringJoiner
  • Sort an int[] descending

Medium

  • Decode the negative result of Arrays.binarySearch to the insertion index
  • Sort employees by department then by pay using two stable sorts

Advanced

  • Compute 100! with BigInteger and its digit sum

Key takeaways

  • Use StringBuilder for repeated appends; Strings are immutable.
  • binarySearch returns -(insertionPoint) - 1 for a miss and needs a sorted array.
  • BigInteger, BitSet and seeded Random solve large-number, compact-flag and reproducible-test needs.