10

Arrays & Strings

One contiguous block of memory — the structure every other structure is built on.

beginner~85 min6 simulations12 problems

▶ guided lesson⚡ 1-minute summary↻ 14 recall cards

before this: Big-O · Java Toolkit

01 · why it exists

Why this exists, and the weight it carries

since1957

Array (contiguous memory)

John Backus and the IBM FORTRAN team

Early programmers kept tables in numbered memory words and worked out every address by hand. FORTRAN, built at IBM from 1954 and described in 1957, made the array part of the language: DIMENSION A(1000) declares 1000 slots, and A(I) reads one. The compiler turns the subscript into an address, so programmers never do that arithmetic again. C, Java and almost every language since kept the same idea: a numbered row of equal-sized slots.

the problem it solvedKeeping many values of one kind under one name and reaching any of them by its number, without hand-written address arithmetic.

its jobs today

  • Every ArrayList, ArrayDeque, HashMap table and String in Java stores its data in a plain array underneath.
  • Images and video frames are arrays of pixels: a 1920 × 1080 screen is 20,73,600 pixel slots, each found by row × width + column.
  • Walking an array in order is the fastest memory access a CPU can do, because caches fetch neighbouring bytes together.

the weight it carries

Illustrative: an int[] of 10 lakh (1,000,000) order amounts. Reading index 7,43,210 is one multiply and one add: base + 743,210 × 4 bytes. Finding the value ₹4,999 instead means checking up to 1,000,000 slots, and inserting at the front shifts all 1,000,000.

without it

No O(1) access by position: every lookup would walk node by node, and everything built on arrays — hash tables, heaps, ArrayList — would lose its speed.

when · why · how

when
You know (or can bound) how many values there are, and you read or write them by position.
why
O(1) read/write by index and the best cache behaviour of any structure. The price: a fixed size, and O(n) to insert or delete in the middle.
how
int[] a = new int[n]; — filled with 0s; a.length is a field. Use Arrays.fill, Arrays.sort and System.arraycopy for bulk work.

02 · plain english

The idea in 30 seconds

An array is a row of equal-sized boxes laid side by side in memory, numbered from 0.

Because every box is the same size, the computer calculates where box i is — so reading any index is O(1).

The price: the row can't stretch in the middle. Inserting or deleting means shifting everything after it — O(n).

picture this

An Excel column. Type A37 in the Name Box and you land on row 37 instantly — Excel never scrolls past rows 1 to 36. But right-click row 3 → Insert, and every row from 3 down has to move one step lower. Jump = O(1), insert in the middle = O(n).

03 · remember

What you must know

  • Contiguous memory: arr[i] lives at base + i × elementSize. One multiply and one add — that's why indexing is O(1).
  • Static vs dynamic: int[] has a fixed length. ArrayList wraps an array and copies it into a bigger one when full — appends are amortized O(1).
  • Shifting is the hidden cost: insert/delete at index i moves n − i elements. Front = O(n), end = O(1).
  • Strings are immutable in Java. s += x in a loop builds a new String each time → O(n²). Use StringBuilder → O(n).
  • Characters are numbers: c - 'a' maps 'a'..'z' to 0..25, so an int[26] is a tiny, fast hash map for letter counts.
  • 2D arrays in Java are arrays of row arrays: grid[r][c]. The Matrices & Simulation chapter turns them into grids you rotate, spiral and simulate in place.
  • Running state beats nested loops: keep a running min, max, sum, product or vote while you scan (Kadane, stock, prefix products, Boyer–Moore) to turn O(n²) into O(n).
  • In-place tricks: reversal, swapping, using the output array as scratch space, or letting index v − 1 remember value v give O(1) extra memory.

04 · see it

Play with it in 3D

Press Space to play, ←/→ to step, drag to move, ? for all shortcuts. Try your own input.

sim

Why arr[i] is O(1) — memory addresses

Each slot sits 4 bytes after the previous one, so the address is computed, never searched for.

loading simulation…
sim

Insert in the middle — watch everything shift

To open a gap at index i, every element from i to the end moves one step right.

loading simulation…
sim

How a dynamic array grows (doubling)

Copies happen only at sizes 1, 2, 4, 8 … — the total stays under 2n, so each append is amortized O(1).

loading simulation…
sim

A frequency array int[26]

Letters become indices with `c - 'a'`. s adds one, t takes one away; anagrams end at all zeros.

loading simulation…
sim

Kadane's algorithm — extend or restart

A negative running sum can only hurt what comes next, so drop it and start fresh.

loading simulation…
sim

Boyer–Moore voting — different values cancel out

One candidate, one counter. Every different value knocks out one vote; the majority is the only value that can survive.

loading simulation…

05 · cost

Time & space, explained

Array (`int[]`) and ArrayList operations
int[]ArrayListwhy
Read / write by indexO(1)O(1)The address is base + i × size: arithmetic, not a search.
Append at the end— (fixed size)O(1) amortizedUsually a free slot exists; the rare O(n) copy is spread over many appends.
Insert / delete at index iO(n)O(n)Everything after i shifts (n − i moves to insert, n − i − 1 to delete).
Insert / delete at the frontO(n)O(n)Every element shifts. Need fast front operations? Use ArrayDeque.
Search by value (unsorted)O(n)O(n)Nothing tells you where a value is — you must look at each slot.
Search by value (sorted)O(log n)O(log n)Binary search halves the range each step.
Grow capacity— (copy to a new array)O(n) when it happensCopy everything into a bigger array (ArrayList grows by ~1.5×).
String and StringBuilder operations
StringStringBuilderwhy
charAt(i) / length()O(1)O(1)Both store characters in an internal array and keep the length.
Append one charO(n) — new copyO(1) amortizedStrings are immutable, so + copies everything. A builder writes into spare capacity.
Build a string of n chars in a loopO(n²)O(n)1 + 2 + … + n characters copied vs one write per char.
substring(a, b)O(b − a)O(b − a)Since Java 7u6 substring copies the characters into a new String.
equals / compareToO(n)compareTo O(n) (Java 11+); equals = identityCompares character by character (String.equals checks the length first). StringBuilder does not override equals — compare sb.toString() instead.
reverse / insert in the middleO(n) via a copyO(n)Every character after the change point has to move.
One-pass array techniques in this chapter
TimeExtra spacewhy
Kadane (max subarray, circular max)O(n)O(1)One decision per element — extend or restart — kept in two or four variables.
Prefix × suffix (product except self)O(n)O(1) besides the outputTwo passes; the left products live in the answer array, the right ones in a single variable.
Boyer–Moore majority voteO(n)O(1)One pass with a candidate and a counter. Counting in a HashMap is also O(n) but needs O(n) memory.
Next permutationO(n)O(1)At most three right-to-left passes over the suffix: find the pivot, find its successor, reverse.
Cyclic placement (first missing positive)O(n)O(1)Every swap settles one value in its home slot for good, so there are at most n swaps in the whole run.
Three reversals (rotate by k)O(n)O(1)Each element is swapped about twice.

06 · java

Raw vs Collections Framework

Reading by index

javabounds-checked O(1) accessjava.util.*
class Indexing {    /** arr[i] lives at base + i * 4 bytes (an int is 4 bytes): one multiply + add, no loop. */    static int read(int[] arr, int i) {        if (i < 0 || i >= arr.length) {            throw new ArrayIndexOutOfBoundsException(i);        }        return arr[i];    }     static void write(int[] arr, int i, int value) {        arr[i] = value; // also O(1)    }}
  • Every Java array access is bounds-checked. A bad index throws ArrayIndexOutOfBoundsException — it never reads random memory like C can.
  • New arrays are filled with defaults: 0 for numbers, false for booleans, '\u0000' for chars, null for objects.
  • arr.length is a field (no parentheses). s.length() is a method. list.size() is a method. Mixing them up is the #1 compile error.

Insert & delete by shifting

javaopen / close a gapjava.util.*
class Shifting {    /** Inserts value at index among the first `size` slots (arr needs one free slot). Returns the new size. */    static int insert(int[] arr, int size, int index, int value) {        for (int i = size; i > index; i--) {            arr[i] = arr[i - 1]; // shift right to open a gap        }        arr[index] = value;        return size + 1;    }     /** Removes arr[index]; everything after it moves one step left. Returns the new size. */    static int delete(int[] arr, int size, int index) {        for (int i = index; i < size - 1; i++) {            arr[i] = arr[i + 1]; // close the gap        }        return size - 1;    }}
  • Shift from the back when inserting (or you overwrite values you still need), from the front when deleting.
  • System.arraycopy(src, from, dst, to, len) does the same shift natively and handles overlapping ranges correctly. ArrayList.add(i, x) uses it internally.

Fixed array vs growable list

javabuild your own (doubling)java.util.*
class DynamicArray {    private int[] data = new int[1];    private int size = 0;     void add(int value) {        if (size == data.length) {            int[] bigger = new int[data.length * 2];            for (int i = 0; i < size; i++) bigger[i] = data[i];            data = bigger;        }        data[size++] = value;    }     int get(int i) {        if (i < 0 || i >= size) throw new IndexOutOfBoundsException(i);        return data[i];    }     int size() { return size; }     int capacity() { return data.length; }}
  • Our version doubles; ArrayList grows by about 1.5× (old + old/2). Any constant factor > 1 gives amortized O(1) appends.
  • new ArrayList<>() starts empty and allocates a 10-slot array on the first add. If you know the size, new ArrayList<>(n) avoids every resize.
  • list.remove(int index) and list.remove(Object o) are different methods. With a List<Integer>, remove(7) removes index 7 — write remove(Integer.valueOf(7)) to remove the value.
  • ArrayList<Integer> stores boxed objects (extra memory, slower). In hot loops prefer int[].

Building strings

javaString += (slow in loops)java.util.*
class SlowConcat {    /** Each += builds a brand-new String and copies everything so far: O(n^2) characters copied. */    static String repeat(char c, int n) {        String s = "";        for (int i = 0; i < n; i++) s += c;        return s;    }}
  • One a + b + c in a single expression is fine — the compiler builds it in one go. The trap is += inside a loop.
  • StringBuilder is not thread-safe and that's fine for interviews. StringBuffer is the old synchronized version — you never need it there.
  • Compare strings with .equals(), never == (which compares object identity, not content).

Characters are numbers

javachar arithmeticjava.util.*
class CharMath {    /** 'a' is 97, 'b' is 98, ... so c - 'a' maps 'a'..'z' to 0..25. */    static int[] letterCounts(String s) {        int[] count = new int[26];        for (int i = 0; i < s.length(); i++) {            count[s.charAt(i) - 'a']++;        }        return count;    }     /** Caesar shift inside 'a'..'z' for k >= 0. */    static char shift(char c, int k) {        return (char) ('a' + (c - 'a' + k) % 26);    }     /** '7' -> 7. */    static int digitValue(char d) {        return d - '0';    }}
  • char is a 16-bit unsigned number (a UTF-16 code unit). 'a' = 97, 'A' = 65, '0' = 48.
  • c - 'a' is an int. To go back, cast: (char) ('a' + i).
  • Use int[26] for lowercase letters, int[128] for ASCII, and a HashMap for full Unicode.

2D arrays

javarows of rowsjava.util.*
class Matrix {    /** int[rows][cols] is an array of row arrays: grid[r][c] = row r, column c. */    static int[] rowSums(int[][] grid) {        int[] sums = new int[grid.length];        for (int r = 0; r < grid.length; r++)            for (int c = 0; c < grid[r].length; c++) // rows may differ in length (jagged)                sums[r] += grid[r][c];        return sums;    }     static int[][] transpose(int[][] grid) {        int rows = grid.length, cols = grid[0].length;        int[][] t = new int[cols][rows];        for (int r = 0; r < rows; r++)            for (int c = 0; c < cols; c++)                t[c][r] = grid[r][c];        return t;    }}
  • int[][] grid = new int[rows][cols] is an array of rows separate int[] objects. grid.length = rows, grid[0].length = cols.
  • Rows can have different lengths (a jagged array), so loop with grid[r].length when that's possible.
  • Walking row by row (r outer, c inner) is faster than column by column — each row is contiguous, which the CPU cache loves.
Java API you will actually use
calldoescost
new int[n]Fixed-size array, filled with 0O(n)
Arrays.fill(a, v)Set every slot to vO(n)
Arrays.copyOf(a, len)Copy (and truncate or pad with 0) into a new arrayO(len)
System.arraycopy(src, i, dst, j, len)Fast native block copy, safe for overlapping rangesO(len)
Arrays.sort(int[])In-place sort of primitives (dual-pivot quicksort)O(n log n)
Arrays.equals(a, b) / Arrays.toString(a)Compare / print contents (a == b only compares references)O(n)
list.add(x) / list.get(i) / list.set(i, x)ArrayList append / read / overwriteO(1) amortized / O(1) / O(1)
list.add(i, x) / list.remove(i)Insert / delete with shiftingO(n)
s.charAt(i) / s.length()Read one char / lengthO(1)
s.toCharArray()Mutable copy of the charactersO(n)
s.substring(a, b)New String of chars a..b−1O(b − a)
sb.append(x) / sb.toString()Grow a StringBuilder / produce the final StringO(1) amortized / O(n)
String.valueOf(char[]) / new String(char[])Turn a char array back into a StringO(n)

Size known up front, primitives, hot loop

→ int[] / char[]

No boxing, no resizing, best cache behaviour.

Unknown size, append-heavy

→ ArrayList

Amortized O(1) appends, O(1) index.

Fast add/remove at both ends

→ ArrayDeque

Circular buffer: O(1) at the front too (ArrayList is O(n) there).

Build a string piece by piece

→ StringBuilder

O(n) total instead of O(n²) with +=.

Count lowercase letters

→ int[26]

Constant space and faster than a HashMap (no hashing, no boxing).

Count arbitrary characters / words

→ HashMap<Character or String, Integer> with merge

Works for any key, O(1) average per update.

07 · recognise

Spot the pattern

"Contiguous subarray" with the max/min sum

Kadane: extend or restart at each index

Same, but the array is circular

max(Kadane max, total − Kadane min), unless everything is negative

"Best pair (i < j)" where the later element is compared with something earlier

Keep a running min/max of the prefix (stock pattern)

"Without division" or "product/sum of everything except i"

Prefix and suffix accumulations

Anagrams, permutations of letters, "same characters"

Frequency array int[26] (or sorted string as a key)

"Appears more than n / 2 times"

Boyer–Moore vote: one candidate, one counter

"Next greater arrangement" or "next permutation"

Pivot from the right, swap with the next bigger value, reverse the suffix

Values in 1..n (or "smallest missing positive") with O(1) space

Index v − 1 is the home of value v: swap values home, or flip signs

"Rotate" or "shift" an array by k in O(1) space

Three reversals

Building a result string in a loop

StringBuilder, never +=

08 · traps & tricks

Mistakes to avoid, tricks to use

traps

  • Off-by-one: valid indices are 0 .. n−1. Loops are i < n, not i <= n.
  • Comparing arrays or strings with == — it compares references. Use Arrays.equals / .equals.
  • s += c inside a loop: silently O(n²). Use StringBuilder.
  • Shifting in the wrong direction while inserting — copy from the back or you overwrite everything with one value.
  • list.remove(i) on a List<Integer> removes by index, not by value.
  • Forgetting k %= n in rotation problems — k can be larger than the array.
  • int overflow when summing or multiplying many values. Check constraints; use long when in doubt.
  • Arrays.asList(int[]) gives a List<int[]> with one element, not a list of ints.
  • Starting a running best at 0 when at least one element must be chosen — all-negative inputs break (Kadane, circular max).

tricks

  • Before coding, ask: "can I solve this with one pass and a few running variables?" That question finds Kadane, stock, prefix products and the majority vote.
  • Say the complexity of library calls out loud — substring and list.add(0, x) are O(n), contains can be O(n·m) in the worst case — and interviewers notice.
  • Test edge cases every time: empty, one element, all equal, all negative, already sorted, k = 0, k > n.
  • When the problem says O(1) extra space, the output array usually doesn't count — you're allowed to use it as scratch space.
  • Stuck? Chalo, dry-run karte hain: take a 5-element example, write the running variables as table columns, and fill one row per index. The pattern usually shows itself by row 3.

09 · why & how

Questions you might be too shy to ask

Why is reading arr[i] O(1) but finding a value O(n)?

Reading by index is arithmetic: base address + i × element size. Finding a value has no formula — the value could be anywhere, so you check slots one by one. Sorting (binary search) or hashing is how you make value lookups fast.

When do I write i < n, i <= n or i < n - 1 in a loop?

Valid indices are 0 .. n − 1, so a plain scan is for (int i = 0; i < n; i++). i <= n reads nums[n] and crashes. If the body reads nums[i + 1], stop one step earlier with i < n - 1, so the last pair is (n − 2, n − 1). If the body reads nums[i - 1], start at i = 1 instead — that's how Stock II compares each day with yesterday. Quick check: put the first and last value of i into every index in the body. Each must land in 0 .. n − 1.

If ArrayList sometimes copies everything, how can append be O(1)?

Because the copies get rarer as the list grows. Take doubling as the simple example: n appends cause copies of 1 + 2 + 4 + … < 2n elements in total. Divide by n appends and each costs a constant on average. That's amortized O(1): a promise about the total, not about every single call. OpenJDK's ArrayList actually grows by about 1.5×. That is an implementation detail — the Javadoc only promises constant amortized time — and any fixed growth factor gives the same O(1).

Why are Java Strings immutable — isn't that wasteful?

Immutability makes strings safe to share between threads, safe to use as HashMap keys (the hash never changes), and lets Java cache them (the string pool). The cost is that every "change" makes a copy — which is exactly why StringBuilder exists.

When should I use int[] instead of ArrayList<Integer>?

When you know the size, or you're in a tight loop. int[] stores raw numbers side by side. ArrayList<Integer> stores references to Integer objects scattered around memory: more memory, more cache misses, and auto-boxing overhead.

What does "in place" actually mean?

You modify the input itself and use only O(1) extra memory (a few variables). Creating a second array of size n is not in place, even if you copy it back at the end.

Does the output array count as extra space?

By the usual interview convention, no — the output is required anyway. LeetCode 238 states this explicitly. Say the convention out loud so the interviewer knows you know it.

Is c - 'a' safe for uppercase letters or digits?

No. 'A' - 'a' is −32, which would crash an int[26]. Normalise first (Character.toLowerCase), or use int[128] indexed by the char itself for any ASCII, or a HashMap for full Unicode.

Subarray, substring, subsequence, subset — aren't they the same thing?

No, and the word changes the whole solution. Subarray (substring for strings): one contiguous block, no gaps. In [1, 2, 3], [2, 3] is one; [1, 3] is not. Subsequence: keep the order but skip anything you like, so [1, 3] is fine. Subset: any choice, order doesn't matter. An array has n(n + 1)/2 non-empty subarrays but 2ⁿ − 1 non-empty subsequences. Kadane and running variables are tools for subarrays; subsequence questions usually need DP.

Kadane never tries every start. How can it be sure it didn't miss the best subarray?

Every subarray ends at some index. At each index i, Kadane keeps the best sum of a subarray ending exactly at i. That best is either nums[i] alone or nums[i] added to the best ending at i − 1. There is no third option. So when the scan reaches the index where the real answer ends, current already holds its sum, and best records it. Example [-2, 5]: at 5, max(5, -2 + 5) = 5. Dropping a negative past never loses anything.

Why does the Boyer–Moore vote work? It feels like magic.

Think of it as pairing off. Every time a value different from the candidate arrives, one of each is thrown away. Each throw-away removes at most one copy of the majority but always one other value. The majority has more copies than everything else combined, so it is the only value that can be left standing.

Next Permutation: why swap the pivot with the rightmost bigger value, not any bigger value?

Everything after the pivot is falling, so reading it from the right gives values in rising order. The first value from the right that beats the pivot is therefore the smallest value bigger than it: the smallest possible step up. [1, 3, 5, 4, 2]: pivot 3. From the right, 2 is too small and 4 wins (not 5). Swap → [1, 4, 5, 3, 2]. The suffix [5, 3, 2] is still falling, which is why one reverse turns it into its smallest order [2, 3, 5].

First Missing Positive has a while loop inside a for loop. How is that O(n)?

Count swaps, not loops. Every swap puts one value into its home slot (index v − 1), and it never moves again. With n slots there can be at most n such swaps in the whole run — so the inner loop does O(n) work in total, not O(n) per i.

I understood Kadane when I read it, but I can't write it myself the next day. Am I just bad at this?

No — normal hai. Reading a solution and producing one are two different skills, and the second one needs practice. Close the solution and write it in a blank file, saying the one-line insight first ("extend or restart"). Stuck on a line? Peek once, then start again from blank — not from where you stopped. Repeat after one day and after three days. The recall cards at the end of the chapter are made for exactly this.

10 · practice

Interview problems

Every problem has brute → better → optimal solutions in Java, with complexity and a dry run.

  1. #121
    Best Time to Buy and Sell Stockmust-do
    EasyNVIDIABloombergAmazonInfosysMeta+1
  2. #122
    Best Time to Buy and Sell Stock II
    MediumAppleAmazonBloombergGoogleMicrosoft
  3. #53
    Maximum Subarraymust-do
    MediumInfosysLinkedInMicrosoftTCSNVIDIA+1
  4. #918
    Maximum Sum Circular Subarray
    MediumAmazonGoogleMicrosoft
  5. #169
    Majority Elementmust-do
    EasyAdobeBloombergMicrosoftAmazonGoogle+1
  6. #238
    Product of Array Except Selfmust-do
    MediumPayPalUberAppleAmazonMicrosoft+1
  7. #242
    Valid Anagrammust-do
    EasyBloombergInfosysTCSAmazonMicrosoft+1
  8. #49
    Group Anagramsmust-do
    MediumAdobeWalmartOracleAmazonApple+1
  9. #14
    Longest Common Prefix
    EasyBloombergAppleGoogleAmazonMicrosoft+1
  10. #189
    Rotate Array
    MediumAppleBloombergMicrosoftSamsungAmazon+1
  11. #31
    Next Permutationmust-do
    MediumMetaAdobeBloombergMicrosoftWalmart+1
  12. #41
    First Missing Positivemust-do
    HardNetflixTCSAmazonMicrosoftGoogle+1

Also practise this here

Each problem has one home page in the course; these use this chapter's idea too. The note says what to look for.

  1. #1
    Two Sum
    Easyin Hashing

    A one-pass array scan where the current element decides what you need to have seen earlier — a HashMap makes that lookup O(1).

  2. #48
    Rotate Image
    Mediumin Matrices

    Reversal on a 2D array: transpose, then reverse every row, for a 90° turn in place.

  3. #54
    Spiral Matrix
    Mediumin Matrices

    Index bookkeeping on a 2D array: four shrinking walls walk an m × n grid in spiral order.

11 · revise

The whole chapter on one screen

10 cards, about a minute. Read them the night before an interview, or right after the chapter to lock it in.

  1. what it is

    An array is one block of equal-sized slots: arr[i] lives at base + i × size, so reading any index is O(1).

  2. cost

    Index O(1) · ArrayList append O(1) amortized · insert/delete in the middle O(n) · search unsorted O(n).

  3. use it when

    Subarray max → Kadane. Except-self → prefix/suffix. Letters → int[26]. More than n/2 → vote. Values 1..n → index v − 1.

  4. template

    Kadane: cur = Math.max(x, cur + x); best = Math.max(best, cur); — start both at nums[0].

  5. template

    Next permutation: pivot = first dip from the right → swap with the rightmost bigger value → reverse the suffix.

  6. trap

    s += c in a loop is O(n²): every += copies the whole string. Build with StringBuilder, call toString() once.

  7. trap

    list.remove(7) on a List<Integer> removes index 7, not the value 7. Write remove(Integer.valueOf(7)).

  8. trap

    Rotate by k: k %= n first. Circular max: all-negative input must return the Kadane max, not total − min.

  9. pro tip

    Before a nested loop, ask: one pass + a few running variables? Running min, best-ending-here, a vote counter.

  10. pro tip

    O(1) space asked? Reverse in place, use the output array as scratch, or let index v − 1 remember value v.

12 · recall

Flip, remember, repeat

card 1 / 14
recall

Why is arr[i] O(1) in Java?

tap or Space to flip ↻

Question 1: Why is arr[i] O(1) in Java?

13 · check yourself

Quick quiz

  1. Why is `arr[i]` O(1) for a Java array?

  2. An ArrayList has 1,000 elements. What does `list.add(0, x)` cost?

  3. What does "amortized O(1)" append mean?

  4. What is the time complexity of this loop? `String s = ""; for (int i = 0; i < n; i++) s += 'a';`

  5. Kadane's algorithm on [-3, -1, -2] returns…

  6. Which key works for grouping anagrams in a `HashMap`?

  7. What is the next permutation of [1, 3, 5, 4, 2]?

  8. How much extra memory does the Boyer–Moore majority vote use?

  9. An array has 6 numbers. The first missing positive is at most…