Learn DSA Part 2: Arrays and Strings - Prefix Sums, Kadane's Algorithm and Matrix Techniques

Learn DSA Part 2: Arrays and Strings - CodingHumans

Arrays and strings are where almost every coding interview starts. They look simple, but most "medium" interview problems are really array problems in disguise: prefix sums, Kadane's algorithm, in-place rearrangement and matrix traversal. If you master the techniques in this article, a big share of interview questions becomes familiar.

This is Part 2 of the Learn DSA series. We cover how arrays work in memory, the cost of each operation, strings in C++ and Java, and the four array techniques you will use again and again. Code is in C++ and Java.

1. How arrays work in memory

An array stores elements of the same type in one contiguous block of memory. Because every element has the same size, the address of element i is simply base + i x size. That is why reading a[i] takes O(1) time no matter how big the array is. Contiguous memory is also cache-friendly, which makes arrays fast in practice.

A static array has a fixed size. A dynamic array (vector in C++, ArrayList in Java) grows automatically by allocating a bigger block, usually about double the size, and copying the elements across.

OperationTimeWhy
Access or update a[i]O(1)Direct address calculation
Search for a value (unsorted)O(n)May need to check every element
Search (sorted, binary search)O(log n)Halve the range each step (Part 8)
Insert or delete at the endO(1) amortizedOccasional resize
Insert or delete at the start or middleO(n)All later elements must shift

2. Strings in C++ and Java

A string is an array of characters, so everything above applies. The important difference between the two languages is mutability:

  • C++ std::string is mutable. You can change s[i] directly, and s += c is amortized O(1).
  • Java String is immutable. Every s = s + c creates a new string and copies all characters, so building a string in a loop this way is O(n^2). Use StringBuilder instead, which appends in amortized O(1).

Most string problems use one of these ideas: count characters with a fixed array of size 26 (for lowercase letters) or a hash map, compare from both ends with two pointers, or use a sliding window over substrings (Part 4).

Subarray vs subsequence vs substring: a subarray or substring is contiguous (no gaps). A subsequence keeps the order but may skip elements. Reading this correctly in a problem statement changes the whole solution.

3. Technique 1: prefix sums

A prefix sum array stores the running total: pre[i] is the sum of the first i elements. After O(n) preprocessing, the sum of any range [l, r] is pre[r + 1] - pre[l] in O(1). Without it, each range query costs O(n).

// Build once in O(n), answer each range sum in O(1)
vector<long long> buildPrefix(const vector<int>& a) {
    vector<long long> pre(a.size() + 1, 0);
    for (size_t i = 0; i < a.size(); i++) pre[i + 1] = pre[i] + a[i];
    return pre;
}
long long rangeSum(const vector<long long>& pre, int l, int r) {
    return pre[r + 1] - pre[l];   // sum of a[l..r]
}
// Build once in O(n), answer each range sum in O(1)
static long[] buildPrefix(int[] a) {
    long[] pre = new long[a.length + 1];
    for (int i = 0; i < a.length; i++) pre[i + 1] = pre[i] + a[i];
    return pre;
}
static long rangeSum(long[] pre, int l, int r) {
    return pre[r + 1] - pre[l];   // sum of a[l..r]
}

Prefix sums combine powerfully with hashing. "Count subarrays with sum k" becomes: for each prefix sum p, how many earlier prefix sums equal p - k? With a hash map that is a single O(n) pass (see Part 3).

4. Technique 2: Kadane's algorithm for maximum subarray

To find the contiguous subarray with the largest sum, the brute force checks every subarray in O(n^2). Kadane's algorithm does it in one pass: at each index, either extend the best subarray ending at the previous index or start fresh here, whichever is larger.

// Maximum subarray sum, O(n) time, O(1) space
long long maxSubarray(const vector<int>& a) {
    long long best = a[0], cur = a[0];
    for (size_t i = 1; i < a.size(); i++) {
        cur = max((long long)a[i], cur + a[i]); // extend or restart
        best = max(best, cur);
    }
    return best;
}
// Maximum subarray sum, O(n) time, O(1) space
static long maxSubarray(int[] a) {
    long best = a[0], cur = a[0];
    for (int i = 1; i < a.length; i++) {
        cur = Math.max(a[i], cur + a[i]);   // extend or restart
        best = Math.max(best, cur);
    }
    return best;
}

This "best answer ending at index i" idea is your first taste of dynamic programming (Part 13). The same pattern solves Best Time to Buy and Sell Stock and maximum product subarray (with a small twist for negative numbers).

5. Technique 3: in-place tricks (reverse and rotate)

Interviewers love asking for O(1) extra space. Two building blocks cover many of these questions:

  • Reverse a range by swapping the two ends and moving inward.
  • Rotate right by k using three reversals: reverse the whole array, then reverse the first k elements, then reverse the rest. Each element moves once, so it is O(n) time and O(1) space.
// Rotate right by k using three reversals: O(n) time, O(1) space
void rotateRight(vector<int>& a, int k) {
    int n = a.size();
    k %= n;
    reverse(a.begin(), a.end());
    reverse(a.begin(), a.begin() + k);
    reverse(a.begin() + k, a.end());
}
// Rotate right by k using three reversals: O(n) time, O(1) space
static void reverse(int[] a, int l, int r) {
    while (l < r) { int t = a[l]; a[l++] = a[r]; a[r--] = t; }
}
static void rotateRight(int[] a, int k) {
    int n = a.length;
    k %= n;
    reverse(a, 0, n - 1);
    reverse(a, 0, k - 1);
    reverse(a, k, n - 1);
}

Other in-place favourites, such as moving zeroes to the end or the Dutch National Flag sort of 0s, 1s and 2s, use the two-pointer pattern from Part 4.

6. Technique 4: 2D arrays and matrices

A 2D array grid[r][c] is an array of rows. Problems usually ask you to traverse it in an unusual order or treat it as a graph:

  • Spiral order: keep four boundaries (top, bottom, left, right) and shrink them after walking each side.
  • Rotate 90 degrees clockwise in place: transpose the matrix (swap a[i][j] with a[j][i]), then reverse each row.
  • Grid as a graph: each cell is a node connected to its 4 neighbours. Counting islands is a BFS or DFS over the grid (Part 11). Use direction arrays dr = {-1,1,0,0} and dc = {0,0,-1,1} to avoid repeating code.

Always check boundaries (0 <= r < rows and 0 <= c < cols) before reading a neighbour. Missing this check is the most common bug in grid problems.

Key takeaways
  • Arrays give O(1) access because elements sit in contiguous memory; inserting or deleting in the middle costs O(n).
  • Java Strings are immutable, so build strings with StringBuilder; C++ strings are mutable.
  • Prefix sums turn range-sum queries into O(1) lookups and pair naturally with hash maps.
  • Kadane's algorithm finds the maximum subarray sum in one O(n) pass.
  • Reversal tricks give O(1)-space rotation; grids are often graphs in disguise.

Practice problems

These are classic LeetCode-style problems, listed from easy to hard. Search for them by name on LeetCode or GeeksforGeeks and solve them before moving on.

  1. Best Time to Buy and Sell Stock (easy)
  2. Move Zeroes (easy)
  3. Valid Anagram (easy)
  4. Longest Common Prefix (easy)
  5. Maximum Subarray (medium, Kadane)
  6. Rotate Array (medium)
  7. Product of Array Except Self (medium, prefix and suffix products)
  8. Subarray Sum Equals K (medium, prefix sum + hash map)
  9. Spiral Matrix (medium)
  10. Rotate Image (medium)
  11. Merge Intervals (medium, sort first)
  12. Trapping Rain Water (hard)

Interview questions and FAQ

When should I use an array instead of a linked list?

Use an array when you need fast random access by index or mostly add at the end; it is also more cache-friendly. A linked list only wins when you insert or delete often in the middle and already hold a pointer to that position.

Why are Java Strings immutable?

Immutability makes strings safe to share between threads, lets the JVM reuse identical literals from the string pool, and keeps hash codes stable for HashMap keys. The cost is that every modification creates a new object.

How does a dynamic array grow, and why is push_back amortized O(1)?

When it is full it allocates a larger block (about 1.5x to 2x) and copies everything. Since the size doubles, the total copying over n pushes is less than 2n, so the average cost per push is constant.

How do I avoid overflow when summing large arrays?

Use a 64-bit type (long long in C++, long in Java) for sums and products, and check the problem constraints: n up to 10^5 with values up to 10^9 can easily exceed the 32-bit limit of about 2.1 x 10^9.

What is the difference between a subarray and a subsequence?

A subarray is contiguous; a subsequence keeps order but can skip elements. An array of n elements has n(n+1)/2 subarrays but 2^n subsequences, which is why subsequence problems often need dynamic programming.

Comments

Answered 0/0Correct 0