Chapter 1: Introduction to Data Structures and Algorithms
1.06Beginner8 topics14 min2 interactive animations

Choosing a solution

Before choosing an algorithm, read the constraints. The input size and time limit usually tell you the target complexity, and the access pattern tells you the structure. This lesson turns that into a checklist.

Learning goals

  • Estimate an allowed complexity from the input size
  • Estimate memory use for common Java structures
  • Choose a structure from the dominant operations
  • List the edge cases to test before submitting

Prerequisites

Algorithm classificationBasic Big-O intuition
1.06.1

Constraints analysis

Constraints are part of the problem statement. “n ≤ 10⁵” and “1 second” already rule out most O(n²) solutions. Reading them first can save hours of designing an approach that cannot possibly be fast enough.

Ask five questions: how large can n be, what is the value range (does it fit in int?), is the input sorted, can there be duplicates, and are there multiple queries over the same data?

1.06.2

Input size

Assume roughly 10⁸ to 10⁹ simple operations per second in Java. Dividing that by the time limit gives a budget, and the input size tells you which growth classes fit within it. Use the calculator to see where each class crosses one second.

These limits are rules of thumb: the constant factor, memory access pattern and JVM warm-up all matter.

Largest nFits within about 1 secondTypical algorithms
10-11O(n!)try all permutations
20-25O(2ⁿ)try all subsets, bitmask DP
~500O(n³)Floyd-Warshall, naive matrix product
~5,000O(n²)nested loops, simple DP over pairs
~10⁶O(n log n)sorting, heaps, balanced trees
~10⁷-10⁸O(n)one or a few passes
anyO(log n) or O(1)binary search, hashing

How fast do the growth classes explode?

Interactive · drag the sliders

O(1)

array index, hash lookup

1 ops

instant

O(log n)

binary search

7 ops

instant

O(√n)

jump search, trial division

10 ops

instant

O(n)

one pass

100 ops

instant

O(n log n)

merge sort

664 ops

instant

O(n²)

nested loops, bubble sort

10,000 ops

10 µs

O(n³)

triple loop, Floyd-Warshall

10^6.0 ops

1.0 ms

O(2ⁿ)

all subsets

10^30.1 ops

10^14 years

O(n!)

all permutations

10^158 ops

10^141 years

Bars use a log scale (the bar shows the exponent of 10). Time assumes 10⁹ simple operations per second, a common rule of thumb for competitive and interview limits. Slide n up and watch O(n²) cross one second near n ≈ 30,000 and O(2ⁿ) become impossible before n reaches 60.

1.06.3

Time limit

A time limit is a budget for the whole program: reading input, computing and printing. For large inputs, slow input and output alone can exceed it, which is why Chapter 2 covers fast readers and StringBuilder/PrintWriter.

If your solution is the right complexity but still too slow, look at constant factors: boxing, HashMap instead of arrays, allocation inside loops and repeated string concatenation.

1.06.4

Memory limit

Estimate memory from the sizes of the types: an int[] of n elements takes about 4n bytes plus a 16-byte header, a long[] 8n, and a boolean[] n. Boxed collections are far larger: an ArrayList<Integer> stores a reference per element plus a separate 16-byte Integer object, and a HashMap<Integer, Integer> entry costs roughly 50-80 bytes.

So 10⁷ ints are about 40 MB as int[] but several hundred MB as List<Integer>. If the limit is tight, prefer primitive arrays.

Structure holding 10⁷ valuesApproximate memory
int[]40 MB
long[]80 MB
boolean[] / BitSet10 MB / 1.25 MB
ArrayList<Integer>about 200 MB (references + Integer objects)
HashMap<Integer, Integer>several hundred MB
1.06.5

Data access patterns

How the data is accessed picks the structure: by position (array), by key (hash map), in sorted order or by range (tree map), always the smallest (heap), first-in first-out (queue), last-in first-out (stack), or by connection (graph).

Write down the three operations your program does most and choose the structure that makes those cheapest.

You mostly…Choose
read by indexarray / ArrayList
look up by keyHashMap
need keys in sorted order or rangesTreeMap / TreeSet
need the smallest or largest repeatedlyPriorityQueue
process in arrival orderArrayDeque as a queue
undo the latest thingArrayDeque as a stack
1.06.6

Operation frequency

Count how often each operation happens, not only how expensive it is. Many reads and few writes favour structures that precompute (sorted array + binary search, prefix sums). Many inserts and few reads favour structures that make inserts cheap (hash set, heap, linked structures).

A query-heavy workload can justify an O(n log n) preprocessing step that makes each of a million queries O(1) or O(log n).

1.06.7

Edge cases

Before submitting, run through the same list every time: empty input, a single element, all elements equal, already sorted and reverse sorted, negative numbers and zero, the maximum and minimum values (overflow), duplicates, and the smallest input that exercises each branch.

Many wrong answers on a first try fail on exactly one of these.

Java
1static int maxOf(int[] a) {
2 if (a.length == 0) throw new IllegalArgumentException("empty array"); // decide, do not guess
3 int best = a[0]; // not 0: all values may be negative
4 for (int x : a) best = Math.max(best, x);
5 return best;
6}
1.06.8

Cost-performance trade-offs

Every choice trades something: time for memory (a hash map speeds up lookups and uses more memory), simplicity for speed (brute force versus a clever structure), and build time for query time (sort once, search many times).

The two-sum pair below shows it. The brute force uses O(1) extra memory and O(n²) time; the hash map version uses O(n) memory and O(n) time. Neither is “better”; the constraints decide.

Brute force: check every pair

Interactive · edit the input

Your input

Try

int[] arr · length 6

7
11
2
15
4
9
012345
Being comparedMatch / answerOuter index
Setup

Try every pair (i, j) with i < j. There are n(n-1)/2 = 15 of them in the worst case.

Step 1 / 10
Java
1public static int[] twoSum(int[] nums, int target) {
2 for (int i = 0; i < nums.length; i++) {
3 for (int j = i + 1; j < nums.length; j++) {
4 if (nums[i] + nums[j] == target) {
5 return new int[] { i, j };
6 }
7 }
8 }
9 return new int[] { -1, -1 };
10}

Live state

0 pairs checked

target

13

n

6

No condition evaluated on this step
Execution trace (dry run table)Show 10 steps
StepLineStateWhat happens
1L2target=13 n=6Try every pair (i, j) with i < j. There are n(n-1)/2 = 15 of them in the worst case.
2L2target=13 i=0Fix i = 0 (value 7) and pair it with every later element.
3L4target=13 i=0 j=1 sum=18Not the target; try the next j.
4L4target=13 i=0 j=2 sum=9Not the target; try the next j.
5L4target=13 i=0 j=3 sum=22Not the target; try the next j.
6L4target=13 i=0 j=4 sum=11Not the target; try the next j.
7L4target=13 i=0 j=5 sum=16Not the target; try the next j.
8L2target=13 i=1Fix i = 1 (value 11) and pair it with every later element.
9L4target=13 i=1 j=2 sum=13This pair adds up to the target.
10L5return=[1, 2]return [1, 2] after 6 pair checks. Simple and obviously correct, but O(n²) in the worst case.

Optimised: remember what you have seen

Interactive · edit the input

Your input

Try

int[] arr · length 6

7
11
2
15
4
9
012345
Being comparedMatch / answerStored in the map

seen (value:index)

empty
Setup

A hash map answers “have I seen this value?” in O(1) on average, so one pass is enough.

Step 1 / 7
Java
1public static int[] twoSum(int[] nums, int target) {
2 Map<Integer, Integer> seen = new HashMap<>(); // value -> index
3 for (int i = 0; i < nums.length; i++) {
4 int need = target - nums[i];
5 if (seen.containsKey(need)) {
6 return new int[] { seen.get(need), i };
7 }
8 seen.put(nums[i], i);
9 }
10 return new int[] { -1, -1 };
11}

Live state

0 lookups

target

13

No condition evaluated on this step
Execution trace (dry run table)Show 7 steps
StepLineStateWhat happens
1L2target=13A hash map answers “have I seen this value?” in O(1) on average, so one pass is enough.
2L5i=0 nums[i]=7 need=613 - 7 = 6, which has not appeared yet.
3L8i=0 size=1Store 7 → 0 for future lookups.
4L5i=1 nums[i]=11 need=213 - 11 = 2, which has not appeared yet.
5L8i=1 size=2Store 11 → 1 for future lookups.
6L5i=2 nums[i]=2 need=1111 is in the map: it was stored at index 1, so that value plus 2 makes 13.
7L6return=[1, 2]return [1, 2] after 3 lookups: O(n) time and O(n) extra memory. Brute force needed O(1) memory but O(n²) time.

Practice ladder

Now try it yourself

Easy

  • With n ≤ 2 × 10⁵ and 2 seconds, which complexities are acceptable?
  • Estimate the memory of an int[1_000_000] and a List<Integer> of the same size

Medium

  • Choose a structure for: 10⁶ lookups by key; the cheapest task first; the last 10 actions
  • List the edge cases for “find the second largest value”

Advanced

  • Design a solution under 64 MB for 10⁸ booleans (hint: BitSet)

Key takeaways

  • Use n and the time limit to pick the target complexity before designing.
  • Estimate memory from primitive sizes; boxed collections are several times bigger.
  • Choose structures by the dominant operations and test the standard edge cases.