01A statement says "return the second largest value". What does the word value tell you?
Return a number from the array, not its position.
02Values reach 10⁹ and n reaches 10⁵. Can the sum of all values pass 2.1 × 10⁹?
Yes. It can reach 10¹⁴, so in Java it needs a long, not an int.
Java gives you every structure an interview needs: lists, hash maps, stacks, queues, heaps and sorted maps. The risk is in the small print. Some calls look like one step but walk the whole list, and some give a wrong number with no error at all.
This lesson lists what each tool costs, the six traps that break correct ideas, and which class to pick for a stack, a queue and a heap.
Arrays and ArrayList
An int[] has a fixed size. An ArrayList grows as you add, and keeps its items in numbered slots like an array.
- Adding or removing at the end touches one slot.
- Adding or removing at the front moves every other item one slot over.
In a test on this laptop with Java 21:
- Emptying 100,000 items with
list.remove(0)took about 0.3 seconds. - Twice the items took about four times as long: the mark of O(n²).
- An
ArrayDequeemptied the same items withpoll()in under a millisecond.
remove(int) or remove(Object)
ArrayList<Integer> has two remove methods. An int argument means a position. An Integer means a value.
List<Integer> list = new ArrayList<>(List.of(10, 20, 30));
list.remove(1); // removes the item at index 1
System.out.println(list); // [10, 30]
list.remove(Integer.valueOf(10)); // removes the value 10
System.out.println(list); // [30]What each operation costs
| Operation | Cost | Why |
|---|---|---|
a[i], a.length, list.get(i), list.set(i, x) | O(1) | one slot |
list.add(x), remove the last item | O(1) on average | the end only |
list.add(0, x), list.remove(0) | O(n) | every item moves |
list.contains(x), list.indexOf(x) | O(n) | scans the list |
Arrays.sort, Collections.sort | O(n log n) | a real sort |
HashMap, HashSet: get, put, add, contains | O(1) on average | hashing |
ArrayDeque: push, pop, offer, poll, peek | O(1) | either end |
PriorityQueue: offer, poll | O(log n) | a heap |
PriorityQueue: peek | O(1) | the top only |
PriorityQueue: contains, remove(x) | O(n) | scans the heap |
TreeMap: get, put, floorKey, ceilingKey | O(log n) | a balanced tree |
s.charAt(i), s.length() | O(1) | one character |
s.substring, s + t, s.toCharArray() | O(n) | builds a new string |
A method call is not one step. list.contains(x) inside a loop is a hidden loop inside a loop: O(n²).
HashMap and HashSet
Two tools solve a large share of array problems:
HashSetanswers "have I seen this?" in one step.HashMapanswers "how many?" or "where?" in one step.
The counting pattern you will write most often:
Map<Character, Integer> count = new HashMap<>();
for (char ch : "banana".toCharArray()) {
count.merge(ch, 1, Integer::sum);
}
System.out.println(count); // {a=3, b=1, n=2}
System.out.println(count.get('z')); // nullmerge(ch, 1, Integer::sum)puts 1 the first time and adds 1 after that.- The longer form does the same:
count.put(ch, count.getOrDefault(ch, 0) + 1). getreturnsnullfor a missing key. Unboxing thatnullinto anintthrows aNullPointerException.
Two rules keep a map or set correct:
- Arrays are compared by identity, not by content. A
Set<int[]>never finds a newint[]{1, 2}. For a grid cell, useList.of(row, col)or the numberrow * cols + colas the key. - A
HashMapkeeps no order. UseLinkedHashMapfor insertion order andTreeMapfor sorted keys.
Stacks, queues and heaps
Java ships all three. Pick these classes:
| You need | Use | Avoid |
|---|---|---|
| stack | ArrayDeque: push, pop, peek | Stack, an old class |
| queue | ArrayDeque: offer, poll, peek | LinkedList, slower |
| min-heap | PriorityQueue | nothing: it is the default |
| max-heap | PriorityQueue with Collections.reverseOrder() | negating values |
| sorted map | TreeMap: floorKey, ceilingKey | sorting again after each change |
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1);
stack.push(2);
System.out.println(stack.pop()); // 2: last in, first out
Deque<Integer> queue = new ArrayDeque<>();
queue.offer(4);
queue.offer(9);
System.out.println(queue.poll()); // 4: first in, first out
PriorityQueue<Integer> minHeap = new PriorityQueue<>(List.of(5, 1, 8));
System.out.println(minHeap.poll()); // 1: smallest first
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
maxHeap.addAll(List.of(5, 1, 8));
System.out.println(maxHeap.poll()); // 8: largest first- The Java docs say
ArrayDequeis likely faster thanStackas a stack, and faster thanLinkedListas a queue. - Why not negate values for a max-heap?
-Integer.MIN_VALUEis still-2147483648, so one value lands in the wrong place.
Six traps
1. int overflows without a warning
An int ends at 2,147,483,647 (2³¹ − 1). One more wraps around to the most negative value.
int big = Integer.MAX_VALUE;
System.out.println(big + 1); // -2147483648
int a = 100_000;
long wrong = a * a; // multiplied as int first
long right = (long) a * a; // cast before the multiply
System.out.println(wrong); // 1410065408
System.out.println(right); // 10000000000- Use
long(up to about 9.2 × 10¹⁸) for sums of many values and for products. long wrong = a * ais still wrong: the multiply happens inint, and only the broken result is widened.
2. Division drops the fraction, and % keeps the sign
System.out.println(7 / 2); // 3
System.out.println(-7 / 2); // -3: rounds toward zero
System.out.println(-7 % 3); // -1: the sign follows the left side
System.out.println(Math.floorMod(-7, 3)); // 2/on twointvalues drops the fraction. For negative values, it rounds toward zero.%can return a negative number.Math.floorModalways returns a value from 0 to m − 1.
You use % 10 and / 10 in the next lesson to take a number apart, digit by digit.
3. == on Integer compares objects
int is a plain value. Integer is an object, and collections hold only objects. Java converts between them for you: .
┌──────────────────────────────────────────────────┐ │ │ │ Integer x = 127, y = 127; x == y is true │ │ x ──┐ │ │ ├──▶ [ 127 ] one shared object │ │ y ──┘ │ │ │ │ Integer p = 128, q = 128; p == q is false │ │ p ─────▶ [ 128 ] object 1 │ │ q ─────▶ [ 128 ] object 2 │ │ │ └──────────────────────────────────────────────────┘
Integer x = 127, y = 127;
System.out.println(x == y); // true
Integer p = 128, q = 128;
System.out.println(p == q); // false
System.out.println(p.equals(q)); // true== asks "the same object?". It passes on small test values and fails on large ones.
This bites when you compare two counts with map.get(a) == map.get(b). Use equals, or store the values in int variables first.
The same rule holds for strings: compare them with equals, never ==.
4. Sorting: int[] or Integer[]
int[] nums = {5, 2, 9};
Arrays.sort(nums); // [2, 5, 9]
Integer[] boxed = {5, 2, 9};
Arrays.sort(boxed, Collections.reverseOrder()); // [9, 5, 2]
Integer[] big = {2_000_000_000, -2_000_000_000, 0};
Arrays.sort(big, (a, b) -> a - b); // [2000000000, -2000000000, 0]
Arrays.sort(big, (a, b) -> Integer.compare(a, b)); // [-2000000000, 0, 2000000000]Arrays.sorton anint[]sorts small to large and takes no comparator. For another order, use anInteger[]or aList<Integer>.a - boverflows when the values are far apart, and the sort comes out wrong. Write comparators withInteger.compare.- To sort pairs by their first item:
Arrays.sort(pairs, (p, q) -> Integer.compare(p[0], q[0])).
5. Strings cannot change
Every String method returns a new string. To build one piece by piece, use a StringBuilder.
String s = "hello";
s.toUpperCase(); // returns "HELLO", s stays "hello"
StringBuilder sb = new StringBuilder();
for (int i = s.length() - 1; i >= 0; i--) sb.append(s.charAt(i));
System.out.println(sb); // olleh
int[] freq = new int[26];
for (char ch : "banana".toCharArray()) freq[ch - 'a']++;
System.out.println(freq[0]); // 3: the count of 'a'result += cin a loop copies the whole string each time: O(n²).appendis O(1) on average.- A
charis a number.ch - 'a'turns'a'to'z'into 0 to 25, a ready index for a count array.
6. The shared list
Arrays.fill puts the same object in every slot. Add to one list, and all of them change.
┌────────────────────────────────────────────┐ │ │ │ Arrays.fill: one shared list │ │ adj[0] ──┐ │ │ adj[1] ──┼──▶ [ 1 ] │ │ adj[2] ──┘ │ │ │ │ a loop: three lists │ │ adj[0] ─────▶ [ 1 ] │ │ adj[1] ─────▶ [ ] │ │ adj[2] ─────▶ [ ] │ │ │ └────────────────────────────────────────────┘
List<Integer>[] bad = new List[3];
Arrays.fill(bad, new ArrayList<>());
bad[0].add(1); // [[1], [1], [1]]
List<List<Integer>> adj = new ArrayList<>();
for (int i = 0; i < 3; i++) adj.add(new ArrayList<>());
adj.get(0).add(1); // [[1], [], []]The loop calls new ArrayList<>() once per slot, so each slot gets its own list. A grid made with new int[3][3] is safe: Java builds a new row for each slot.
- 01
Work at the end of an ArrayList. remove(0) and add(0, x) are O(n). Use ArrayDeque for a queue.
- 02
An int ends at about 2.1 × 10⁹. Use long for sums and products, and cast before you multiply.
- 03
Compare Integer and String objects with equals, and write comparators with Integer.compare.
- 04
Use ArrayDeque for stacks and queues, PriorityQueue for a min-heap, and Collections.reverseOrder() for a max-heap.
Read more
- ArrayList in the Java 21 API, with the cost of each operation
- ArrayDeque in the Java 21 API, the class to use as a stack or a queue
- PriorityQueue in the Java 21 API, on the heap order and its costs
- Integer.valueOf(int) in the Java 21 API, on the cache from -128 to 127
Next: working with digits. Skip the other toolbox lessons unless you code in those languages too. You will take a number apart with % and /.