Progress
🌍
Why does this topic matter?
Arrays and Strings are the most common interview topic at every FAANG company — by a significant margin. Virtually every other data structure (stack, queue, hash map, graph adjacency list) is implemented on top of an array internally. Mastering array patterns (prefix sums, two pointers, sliding window, Dutch National Flag) and string patterns (frequency arrays, rolling hash, StringBuilder) is non-negotiable. If you solve only one lecture deeply, make it this one.

📖 What is an Array?

An array is a fixed-size, contiguous block of memory that holds elements of the same type. When you write int[] arr = new int[5] in Java, the JVM reserves 5×4 = 20 bytes of contiguous heap memory, all zeroed out. The address of arr[0] is some base address B, and arr[i] is at B + i×4. This is why random access is O(1) — you don't search; you calculate.

Array: int[] arr = {10, 20, 30, 40, 50} Memory layout (contiguous): ┌──────┬──────┬──────┬──────┬──────┐ │ 10 │ 20 │ 30 │ 40 │ 50 │ └──────┴──────┴──────┴──────┴──────┘ [0] [1] [2] [3] [4] ↑ Base address B addr(arr[i]) = B + i × 4 → O(1) access!

📐 What are 2D Arrays?

A 2D array is an array of arrays — a table with rows and columns. In Java, int[][] matrix = new int[3][4] creates 3 separate 1D arrays, each of size 4. The rows are NOT necessarily contiguous in memory (unlike C's 2D arrays), because each row is an independent object on the heap.

int[][] grid = new int[3][4] → 3 rows, 4 columns grid[0] → [ 0 | 0 | 0 | 0 ] ← one int[] object on heap grid[1] → [ 0 | 0 | 0 | 0 ] ← another int[] object grid[2] → [ 0 | 0 | 0 | 0 ] ← another int[] object Accessing grid[r][c]: • grid[r] → gets the r-th row array O(1) • [c] → gets the c-th element O(1) Traversal: row by row is cache-friendly ✓ column by column is NOT ✗
💡
2D Array Patterns you must know:
Row/Column traversal: nested for loops
Diagonal traversal: elements where i == j (main) or i + j == n-1 (anti-diagonal)
Transpose: swap grid[i][j]grid[j][i]
Spiral order: shrinking boundary: top/bottom/left/right
2D Prefix Sum: O(1) submatrix sum queries (covered in this lecture)

🧵 What is a String in Java? — The Deep Dive

In Java, a String is an immutable sequence of char values. Internally, it is backed by a char[] (before Java 9) or a byte[] with a coder flag (Java 9+ compact strings). "Immutable" means once a String object is created, it cannot be changed — ever. Any operation that appears to "modify" a string actually creates a brand new String object.

String s = "hello"; s = s + " world"; // What ACTUALLY happens: Step 1: Create new object "hello world" (new heap allocation) Step 2: Point s to the new object Step 3: "hello" is now UNREFERENCED → eligible for GC If done 1000 times in a loop: → 1000 new String objects created! → O(1+2+3+...+N) = O(N²) time ← The String concatenation trap
⚠️
The StringBuilder Fix: StringBuilder is a mutable buffer. It holds a resizable char[] array internally and appends directly — no new objects. Use StringBuilder inside any loop that builds a string.

Complexity comparison:
String += char in a loop → O(N²) time
StringBuilder.append(char) in a loop → O(N) time, O(1) amortized per append
☕ Java · String vs StringBuilder Deep Dive
// String — immutable, stored in String Pool
String s1 = "hello";       // String Pool object
String s2 = "hello";       // SAME pool object! s1 == s2 is TRUE
String s3 = new String("hello"); // NEW heap object. s1 == s3 is FALSE
// Always use .equals() not == to compare String content!
s1.equals(s2)  // → true  ✓
s1.equals(s3)  // → true  ✓
s1 == s3       // → false ← TRAP! different heap objects

// Key String methods (all return NEW String objects):
s.length()                  // O(1) — stored as field
s.charAt(i)                 // O(1) — array lookup
s.substring(l, r)           // O(r-l) — copies chars
s.indexOf("sub")            // O(N*M) naive search
s.toCharArray()             // O(N) — creates new char[]
s.toLowerCase()             // O(N) — new String
s.trim()                    // O(N) — new String
String.valueOf(42)          // Integer → String
Integer.parseInt("42")     // String → int

// StringBuilder — mutable, heap-allocated buffer
StringBuilder sb = new StringBuilder();
sb.append("hello");          // O(1) amortised
sb.append(' ');
sb.append("world");
sb.insert(5, ",");            // O(N) — shifts chars
sb.delete(5, 6);             // O(N) — shifts chars
sb.reverse();                  // O(N) — in-place reverse
sb.toString();                 // O(N) — creates final String
Operation String StringBuilder
Access char at index O(1) charAt(i) O(1) charAt(i)
Append single char O(N) — creates new String O(1) amortised
Append N chars in loop O(N²) total O(N) total
Reverse O(N) new String O(N) in-place
Thread safety ✅ Immutable = always safe ❌ Not thread-safe (use StringBuffer)
Memory String Pool deduplication Always new heap object
🔗
How it connects: Everything you learned about Java's heap in Lecture 2 applies here — every String is a heap object. Lecture 7 (Math) uses numeric string parsing (n % 10 to extract digits). The Hashing lecture (part of Phase 2) builds directly on frequency arrays introduced here.

💡
Why Arrays matter at scale: Arrays are the single most cache-friendly data structure in computer science. Modern CPUs pull contiguous chunks of memory into the L1 cache. A linear scan of an array is orders of magnitude faster than traversing a Linked List, even if both are O(N).

📊 Prefix Sums (1D and 2D)

Imagine your bank account. If you want to know exactly how much you spent between Wednesday and Friday, you don't need to manually add up Wednesday + Thursday + Friday's receipts. You can simply look at your total balance on Friday and subtract your total balance from Tuesday!

This is the essence of Prefix Sums. They allow you to answer multiple Range Sum Queries in O(1) time after a single O(N) precomputation step.

1.1 The 1D Memory Logic

We build a new array P where P[i] stores the sum of all elements from index 0 up to i.

// Original Array (Daily Expenses): arr = [ 3, 1, 4, 1, 5 ] // Prefix Sum Array (Cumulative Spending): P[0] = 3 P[1] = P[0] + 1 = 4 P[2] = P[1] + 4 = 8 P[3] = P[2] + 1 = 9 P[4] = P[3] + 5 = 14 P = [ 3, 4, 8, 9, 14 ]
📝 Prefix Sum · Logic
// Step 1: Precompute (Build Prefix Array)
P[0] = A[0]
for (int i = 1; i < N; i++) {
   P[i] = P[i-1] + A[i];
}

// Step 2: Query(L, R) in O(1)
return P[R] - (L > 0 ? P[L-1] : 0);

1.2 The 2D Submatrix Logic

What if you have a 2D matrix (like a grid of numbers) and you want the sum of a specific rectangle? Treating the matrix as a series of rectangles. Doing nested loops takes an incredibly slow O(N²M²). Using the Inclusion-Exclusion Principle, you can find the sum of any sub-rectangle in O(1).

🚀
Optimization Hint: If you are updating values frequently and querying ranges, a vanilla Prefix Array is slow (O(N) updates). Instead, use a Fenwick Tree (BIT) to get O(log N) updates and queries.
Operation Formula Statement
Precompute `P[i][j]` arr[i][j] + P[i-1][j] + P[i][j-1] - P[i-1][j-1]
Add the cell itself + rectangle ABOVE + rectangle LEFT. Subtract corner added twice.
Query `(r1,c1)` to `(r2,c2)` P[r2][c2] - P[r1-1][c2] - P[r2][c1-1] + P[r1-1][c1-1]
Take giant rectangle. Cut top. Cut left. Add top-left corner back once.
📝 2D Submatrix · Logic
// To find sum from (r1, c1) to (r2, c2):
Sum = P[r2][c2] - P[r1-1][c2] - P[r2][c1-1] + P[r1-1][c1-1];

🎒 Kadane's Algorithm — Maximum Subarray

If you're looking for the largest possible sum from a contiguous chunk of an array, checking every chunk takes O(n³). Kadane's algorithm finds the answer in a blazingly fast O(N) single pass!

2.1 The "Reset" Philosophy

Kadane’s works on a simple intuition: If my past is dragging me down (negative sum), I must drop the baggage and start fresh!

At every step, you must make a greedy choice: Do I append the current number to the existing subarray chain, OR do I cut ties and start a brand-new subarray at this exact number?

Let's dry run the array: [-2, 1, -3, 4] [ -2 ] -> currentMax = -2. [ 1 ] -> Do I append 1 to -2 (gives -1)? Or start fresh at 1? (1 > -1. Start fresh!) currentMax = 1 [ -3 ] -> Do I append -3 to 1 (gives -2)? Or start fresh at -3? (-2 > -3. Keep the chain!) currentMax = -2 [ 4 ] -> Do I append 4 to -2 (gives 2)? Or start fresh at 4? (4 > 2. Drop the baggage!) currentMax = 4 Global Max recorded during this trip: 4
☕ Java · Maximum Subarray
public int maxSubArray(int[] nums) {
    int currentMax = nums[0];
    int globalMax = nums[0];
    
    for (int i = 1; i < nums.length; i++) {
        // Core Philosophy: Accept the baggage or ditch it?
        currentMax = Math.max(nums[i], currentMax + nums[i]);
        globalMax = Math.max(globalMax, currentMax);
    }
    return globalMax;
}
🌟
Extensions: You can apply Kadane's logic to find the Max Product Subarray (track both minProduct and maxProduct) or the Best Time to Buy & Sell Stock (track the minPrice).

🚩 Dutch National Flag (Three-Way Partition)

Invented by Edsger Dijkstra, the DNF approach sorts 0s, 1s, and 2s in a single pass using O(1) space. It maintains 4 strictly defined zones using 3 pointers: low, mid, and high.

☕ Java · Sort Colors
public void sortColors(int[] nums) {
    int low = 0, mid = 0, high = nums.length - 1;
    while (mid <= high) {
        if (nums[mid] == 0) {
            swap(nums, low++, mid++);
        } else if (nums[mid] == 1) {
            mid++;
        } else {
            swap(nums, mid, high--);
        }
    }
}

🔄 The Reversal Algorithm (In-Place)

Rotating an array by K steps in O(1) space using three strategic mirror flips.

🔄
The Trick: To rotate [1, 2, 3, 4, 5] by K=2:
  1. Reverse whole: [5, 4, 3, 2, 1]
  2. Reverse first K: [4, 5, 3, 2, 1]
  3. Reverse rest: [4, 5, 1, 2, 3]
☕ Java · Array Rotation
public void rotate(int[] nums, int k) {
    int n = nums.length;
    k %= n;
    reverse(nums, 0, n - 1);
    reverse(nums, 0, k - 1);
    reverse(nums, k, n - 1);
}

🔤 Frequency Arrays vs HashMaps

HashMaps can be slow for constant character sets like lowercase English letters. A flat int[26] array is the absolute fastest way to count frequencies.

☕ Java · Anagram Check
int[] count = new int[26];
for (char c : s.toCharArray()) {
    count[c - 'a']++;
}

🧵 String Immutability

Strings in many languages are immutable. Operations like s += "abc" create brand new objects, leading to O(N²) time when done in loops.

⚠️
The Trap: Never use += on a String inside a loop. Always use StringBuilder for high-performance mutations.
☕ Java · StringBuilder
StringBuilder sb = new StringBuilder();
for (char c : chars) sb.append(c);
return sb.toString();

🔑 Rabin-Karp & Rolling Hash

Rabin-Karp enables O(N) substring searching by converting windows to numerical hashes. The Rolling Hash allows O(1) hash updates as the window slides.

📝 Rolling Hash · Concept
\[ H_{new} = (H_{old} - S[i-1] \cdot P^{M-1}) \cdot P + S[i+M-1] \]

🌀 2D Boundary Traversals

Navigating matrices in orders like Spiral or Zigzag is easiest when using 4 dynamic, shrinking walls: top, bottom, left, and right.

☕ Java · Spiral Matrix
while (top <= bottom && left <= right) {
    // Paint Top wall, increment top
    // Paint Right wall, decrement right
    // Paint Bottom wall, decrement bottom
    // Paint Left wall, increment left
}

💪 Practice Problems

Master these core patterns through hands-on practice.

Problem 01 · 2D In-Place
Rotate Image
Medium AmazonMicrosoft Matrix
Problem 02 · Kadane's
Maximum Subarray Sum
Medium GoogleAmazon Dynamic Programming
Problem 03 · Prefix Products
Product of Array Except Self
Medium AmazonMeta Prefix/Suffix
Problem 04 · Two Pointers
Container With Most Water
Medium GoogleAmazon Two Pointers
Problem 05 · Prefix Sum + HashMap
Subarray Sum Equals K
Medium FacebookGoogle Prefix Sum
Problem 06 · Sliding Window
Minimum Size Subarray Sum
Medium Amazon Sliding Window
Problem 07 · Frequency Array
Valid Anagram
Easy Amazon Freq Array
Problem 08 · String Column Scan
Longest Common Prefix
Easy Google String

📝 Assignment

📋
Topic 8 Assignment — 35 Problems
Complete the comprehensive practice set covering Prefix Sums, Kadane's, Matrix manipulations, and String logic.

Open Assignment.md →

✅ Topic 8 Completion Checklist

I understand why `String` is immutable in Java.
I have mastered the Two-Pointer and Sliding Window patterns.
I can explain Kadane's logic (baggage reset) out loud.
I have solved at least 15 medium problems from the assignment.
🧠
You're ready for Topic 9: Sorting Algorithms
Master the physics of data organization. From Merge Sort's Divide & Conquer to QuickSort's Partitioning — foundations for advanced recursion.