
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.
| Operation | Time | Why |
|---|---|---|
| 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 end | O(1) amortized | Occasional resize |
| Insert or delete at the start or middle | O(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::stringis mutable. You can changes[i]directly, ands += cis amortized O(1). - Java
Stringis immutable. Everys = s + ccreates a new string and copies all characters, so building a string in a loop this way is O(n^2). UseStringBuilderinstead, 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).
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]witha[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}anddc = {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.
- 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.
- Best Time to Buy and Sell Stock (easy)
- Move Zeroes (easy)
- Valid Anagram (easy)
- Longest Common Prefix (easy)
- Maximum Subarray (medium, Kadane)
- Rotate Array (medium)
- Product of Array Except Self (medium, prefix and suffix products)
- Subarray Sum Equals K (medium, prefix sum + hash map)
- Spiral Matrix (medium)
- Rotate Image (medium)
- Merge Intervals (medium, sort first)
- 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
Post a Comment