Array Data Structure and Operations
1. Array Operations (Insert, Delete, Access)
| Operation | Time Complexity | Description | Use Case |
|---|---|---|---|
| Access by Index | O(1) |
Direct memory address calculation: base + index × size | Fast random access to elements |
| Search (Unsorted) | O(n) |
Linear scan through all elements | Finding element in unordered array |
| Search (Sorted) | O(log n) |
Binary search on sorted array | Efficient searching when sorted |
| Insert at End | O(1) |
Add element after last index (if space available) | Appending elements efficiently |
| Insert at Position | O(n) |
Shift all elements after insertion point right | Maintaining order while inserting |
| Delete at End | O(1) |
Remove last element, decrease size | Stack-like pop operation |
| Delete at Position | O(n) |
Shift all elements after deletion point left | Removing element while maintaining order |
| Traverse/Iterate | O(n) |
Visit each element sequentially | Processing all elements |
Example: Basic Array Operations
// Array creation and initialization
const arr = [1, 2, 3, 4, 5];
const zeros = new Array(5).fill(0); // [0, 0, 0, 0, 0]
// Access - O(1)
const first = arr[0]; // 1
const last = arr[arr.length - 1]; // 5
// Search - O(n)
function linearSearch(arr, target) {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === target) return i;
}
return -1;
}
// Insert at end - O(1) amortized
arr.push(6); // [1, 2, 3, 4, 5, 6]
// Insert at position - O(n)
function insertAt(arr, index, value) {
arr.splice(index, 0, value);
return arr;
}
insertAt(arr, 2, 10); // [1, 2, 10, 3, 4, 5, 6]
// Delete at end - O(1)
arr.pop(); // [1, 2, 10, 3, 4, 5]
// Delete at position - O(n)
arr.splice(2, 1); // [1, 2, 3, 4, 5]
// Traverse - O(n)
arr.forEach((val, idx) => console.log(`arr[${idx}] = ${val}`));
| Common Techniques | Approach | Time | Application |
|---|---|---|---|
| Two Pointers | Use two indices moving toward/away from each other | O(n) |
Reverse array, find pairs, palindrome check |
| Sliding Window | Maintain window of fixed/variable size | O(n) |
Subarray sum, max window, substring problems |
| Prefix Sum | Precompute cumulative sums for range queries | O(n) prep, O(1) query |
Range sum queries, subarray problems |
| Hash Map | Store element-index mappings for O(1) lookup | O(n) |
Two sum, frequency counting, duplicates |
Example: Two Pointers Technique
// Reverse array in-place
function reverseArray(arr) {
let left = 0, right = arr.length - 1;
while (left < right) {
[arr[left], arr[right]] = [arr[right], arr[left]];
left++;
right--;
}
return arr;
}
// Find pair with given sum (sorted array)
function twoSum(arr, target) {
let left = 0, right = arr.length - 1;
while (left < right) {
const sum = arr[left] + arr[right];
if (sum === target) return [left, right];
if (sum < target) left++;
else right--;
}
return null;
}
Example: Prefix Sum
// Build prefix sum array
function buildPrefixSum(arr) {
const prefix = [arr[0]];
for (let i = 1; i < arr.length; i++) {
prefix[i] = prefix[i - 1] + arr[i];
}
return prefix;
}
// Range sum query O(1)
function rangeSum(prefix, left, right) {
if (left === 0) return prefix[right];
return prefix[right] - prefix[left - 1];
}
// Example usage
const arr = [1, 2, 3, 4, 5];
const prefix = buildPrefixSum(arr);
console.log(rangeSum(prefix, 1, 3)); // 9 (2+3+4)
2. 2D Arrays and Multi-dimensional Arrays
| Traversal Pattern | Direction | Time Complexity | Use Case |
|---|---|---|---|
| Row-major Order | Left to right, top to bottom | O(m × n) |
Standard matrix traversal, most common in memory layout |
| Column-major Order | Top to bottom, left to right | O(m × n) |
Column operations, vertical processing |
| Spiral Traversal | Clockwise from outer to inner layers | O(m × n) |
Matrix printing, spiral pattern generation |
| Diagonal Traversal | Along diagonals (top-left to bottom-right) | O(m × n) |
Diagonal matrix operations, DP problems |
| Zigzag Traversal | Alternating left-right, right-left | O(m × n) |
Binary tree level order variations |
| Layer-by-layer | Process outer layer, then inner recursively | O(m × n) |
Rotate matrix, boundary processing |
Example: Matrix Traversal Patterns
// Row-major traversal
function rowMajor(matrix) {
const result = [];
for (let i = 0; i < matrix.length; i++) {
for (let j = 0; j < matrix[0].length; j++) {
result.push(matrix[i][j]);
}
}
return result;
}
// Column-major traversal
function columnMajor(matrix) {
const result = [];
for (let j = 0; j < matrix[0].length; j++) {
for (let i = 0; i < matrix.length; i++) {
result.push(matrix[i][j]);
}
}
return result;
}
// Spiral traversal
function spiralOrder(matrix) {
const result = [];
let top = 0, bottom = matrix.length - 1;
let left = 0, right = matrix[0].length - 1;
while (top <= bottom && left <= right) {
// Right
for (let j = left; j <= right; j++) result.push(matrix[top][j]);
top++;
// Down
for (let i = top; i <= bottom; i++) result.push(matrix[i][right]);
right--;
// Left
if (top <= bottom) {
for (let j = right; j >= left; j--) result.push(matrix[bottom][j]);
bottom--;
}
// Up
if (left <= right) {
for (let i = bottom; i >= top; i--) result.push(matrix[i][left]);
left++;
}
}
return result;
}
// Diagonal traversal
function diagonalTraversal(matrix) {
const result = [];
const m = matrix.length, n = matrix[0].length;
// Upper diagonals (starting from first row)
for (let d = 0; d < n; d++) {
let i = 0, j = d;
while (i < m && j >= 0) {
result.push(matrix[i][j]);
i++;
j--;
}
}
// Lower diagonals (starting from first column)
for (let d = 1; d < m; d++) {
let i = d, j = n - 1;
while (i < m && j >= 0) {
result.push(matrix[i][j]);
i++;
j--;
}
}
return result;
}
Note: For cache efficiency, row-major traversal is preferred in most languages (C, Java, JavaScript) as it accesses consecutive memory locations. Column-major traversal can cause cache misses.
3. Dynamic Arrays (ArrayList, Vector Resizing)
| Strategy | Growth Factor | Amortized Insert | Space Overhead |
|---|---|---|---|
| Double When Full | 2× capacity | O(1) |
Up to 100% (worst case 50% wasted) |
| 1.5× Growth | 1.5× capacity | O(1) |
~33% overhead, better memory reuse |
| Golden Ratio | φ ≈ 1.618× capacity | O(1) |
Optimal for memory block reuse |
| Fixed Increment | capacity + k | O(n) |
Low space overhead but slower growth |
| Aspect | Doubling (2×) | 1.5× Growth | Trade-offs |
|---|---|---|---|
| Resize Frequency | Fewer resizes (log₂ n) | More frequent resizes (log₁.₅ n) | Doubling: less overhead, 1.5×: steadier growth |
| Memory Reuse | Cannot reuse freed blocks | Can reuse previously freed memory | 1.5× better for memory fragmentation |
| Space Waste | Up to 50% capacity unused | Up to 33% capacity unused | 1.5× more space efficient |
| Implementation | JavaScript, Java, Python use variants of doubling | Used in some C++ STL implementations | Most systems use 1.5× to 2× range |
Example: Dynamic Array Implementation
class DynamicArray {
constructor(initialCapacity = 2) {
this.data = new Array(initialCapacity);
this.size = 0;
this.capacity = initialCapacity;
}
// O(1) amortized
push(value) {
if (this.size === this.capacity) {
this.resize(this.capacity * 2); // Double capacity
}
this.data[this.size++] = value;
}
// O(1)
pop() {
if (this.size === 0) return undefined;
const value = this.data[--this.size];
// Shrink if utilization < 25%
if (this.size > 0 && this.size < this.capacity / 4) {
this.resize(Math.floor(this.capacity / 2));
}
return value;
}
// O(n) for resize operation
resize(newCapacity) {
const newData = new Array(newCapacity);
for (let i = 0; i < this.size; i++) {
newData[i] = this.data[i];
}
this.data = newData;
this.capacity = newCapacity;
}
// O(1)
get(index) {
if (index < 0 || index >= this.size) {
throw new Error("Index out of bounds");
}
return this.data[index];
}
getSize() { return this.size; }
getCapacity() { return this.capacity; }
}
// Usage
const arr = new DynamicArray();
for (let i = 0; i < 10; i++) {
arr.push(i);
console.log(`Size: ${arr.getSize()}, Capacity: ${arr.getCapacity()}`);
}
// Size: 1, Capacity: 2
// Size: 2, Capacity: 2
// Size: 3, Capacity: 4
// Size: 4, Capacity: 4
// Size: 5, Capacity: 8
// ...
Warning: When shrinking dynamic arrays, use hysteresis (e.g., shrink at 25% full, not 50%) to avoid thrashing - repeated resize operations when size oscillates around threshold.
4. Sparse Arrays (Memory Optimization)
| Representation | Space Complexity | Access Time | Best For |
|---|---|---|---|
| Dictionary/Hash Map | O(k) where k = non-zero elements |
O(1) average |
Very sparse arrays, random access pattern |
| Coordinate List (COO) | O(k) - stores (index, value) pairs |
O(k) - requires search |
Building sparse structures, easy construction |
| Compressed Sparse Row (CSR) | O(k + rows) |
O(log k) per row |
Matrix operations, row-wise access, scientific computing |
| Run-Length Encoding | O(runs) where runs = consecutive segments |
O(runs) |
Long sequences of same values (images, game boards) |
| Bit Vector | O(n/8) - one bit per element |
O(1) |
Boolean arrays, presence/absence tracking |
Example: Sparse Array Implementations
// HashMap-based sparse array
class SparseArray {
constructor() {
this.data = new Map();
this.defaultValue = 0;
}
set(index, value) {
if (value !== this.defaultValue) {
this.data.set(index, value);
} else {
this.data.delete(index); // Don't store default values
}
}
get(index) {
return this.data.has(index) ? this.data.get(index) : this.defaultValue;
}
getSize() {
return this.data.size; // Number of non-default values
}
}
// Coordinate List (COO) format - for sparse matrix
class COOMatrix {
constructor() {
this.rows = [];
this.cols = [];
this.values = [];
}
add(row, col, value) {
if (value !== 0) {
this.rows.push(row);
this.cols.push(col);
this.values.push(value);
}
}
get(row, col) {
for (let i = 0; i < this.rows.length; i++) {
if (this.rows[i] === row && this.cols[i] === col) {
return this.values[i];
}
}
return 0;
}
}
// Run-Length Encoding
class RLEArray {
constructor() {
this.runs = []; // [{value, count}, ...]
}
encode(arr) {
if (arr.length === 0) return;
let currentValue = arr[0];
let count = 1;
for (let i = 1; i < arr.length; i++) {
if (arr[i] === currentValue) {
count++;
} else {
this.runs.push({value: currentValue, count});
currentValue = arr[i];
count = 1;
}
}
this.runs.push({value: currentValue, count});
}
decode() {
const result = [];
for (const run of this.runs) {
for (let i = 0; i < run.count; i++) {
result.push(run.value);
}
}
return result;
}
}
// Example usage
const sparse = new SparseArray();
sparse.set(1000, 42);
sparse.set(5000, 99);
console.log(sparse.get(1000)); // 42
console.log(sparse.get(2000)); // 0 (default)
console.log(sparse.getSize()); // 2 (only stores non-zero values)
5. Array Rotation (Left Rotation, Right Rotation)
| Technique | Algorithm | Time | Space |
|---|---|---|---|
| Rotate Left by k | Reversal algorithm: reverse(0,k-1), reverse(k,n-1), reverse(0,n-1) | O(n) |
O(1) |
| Rotate Right by k | Same as left rotate by (n-k) | O(n) |
O(1) |
| Cyclic Rotation | GCD-based approach for in-place rotation | O(n) |
O(1) |
| Transpose Matrix | Swap elements across diagonal: matrix[i][j] ↔ matrix[j][i] | O(n²) |
O(1) |
| Rotate Matrix 90° | Transpose then reverse each row (or column) | O(n²) |
O(1) |
| Partition (Dutch Flag) | Three-way partitioning for sorting 0s, 1s, 2s | O(n) |
O(1) |
Example: Array Rotation
// Rotate left by k positions - Reversal Algorithm
function rotateLeft(arr, k) {
const n = arr.length;
k = k % n; // Handle k > n
reverse(arr, 0, k - 1); // Reverse first k
reverse(arr, k, n - 1); // Reverse remaining
reverse(arr, 0, n - 1); // Reverse entire array
return arr;
}
function reverse(arr, start, end) {
while (start < end) {
[arr[start], arr[end]] = [arr[end], arr[start]];
start++;
end--;
}
}
// Rotate right by k positions
function rotateRight(arr, k) {
return rotateLeft(arr, arr.length - k);
}
// Example
const arr = [1, 2, 3, 4, 5];
rotateLeft(arr, 2); // [3, 4, 5, 1, 2]
Example: Matrix Rotation 90°
// Rotate matrix 90° clockwise in-place
function rotateMatrix90(matrix) {
const n = matrix.length;
// Step 1: Transpose
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
[matrix[i][j], matrix[j][i]] =
[matrix[j][i], matrix[i][j]];
}
}
// Step 2: Reverse each row
for (let i = 0; i < n; i++) {
matrix[i].reverse();
}
return matrix;
}
// Example
const mat = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
rotateMatrix90(mat);
// Result: [[7,4,1], [8,5,2], [9,6,3]]
Example: Dutch National Flag Problem
// Sort array of 0s, 1s, and 2s in-place
function dutchFlagPartition(arr) {
let low = 0; // Boundary for 0s
let mid = 0; // Current element
let high = arr.length - 1; // Boundary for 2s
while (mid <= high) {
if (arr[mid] === 0) {
[arr[low], arr[mid]] = [arr[mid], arr[low]];
low++;
mid++;
} else if (arr[mid] === 1) {
mid++;
} else { // arr[mid] === 2
[arr[mid], arr[high]] = [arr[high], arr[mid]];
high--;
}
}
return arr;
}
// Example
const colors = [2, 0, 1, 2, 1, 0, 1, 2, 0];
dutchFlagPartition(colors); // [0, 0, 0, 1, 1, 1, 2, 2, 2]
// Time: O(n), Space: O(1), Single pass!
6. Kadane's Algorithm (Maximum Subarray Sum)
| Problem | Algorithm | Time | Key Insight |
|---|---|---|---|
| Maximum Subarray Sum | Kadane's Algorithm | O(n) |
Track max ending here vs. max so far |
| Maximum Product Subarray | Track both max and min products | O(n) |
Negative × negative = positive, need min for negative |
| Longest Subarray Sum K | Prefix sum + hash map | O(n) |
If prefix[j] - prefix[i] = k, found subarray |
| Subarray Sum Equals K | Prefix sum + hash map for count | O(n) |
Count occurrences of (currentSum - k) in map |
| Maximum Circular Subarray | Max(normal Kadane, total - min subarray) | O(n) |
Circular max = total sum - minimum subarray sum |
Example: Kadane's Algorithm - Maximum Subarray Sum
// Find maximum sum of contiguous subarray
function maxSubarraySum(arr) {
let maxSoFar = arr[0]; // Global maximum
let maxEndingHere = arr[0]; // Maximum ending at current position
for (let i = 1; i < arr.length; i++) {
// Either extend existing subarray or start new one
maxEndingHere = Math.max(arr[i], maxEndingHere + arr[i]);
maxSoFar = Math.max(maxSoFar, maxEndingHere);
}
return maxSoFar;
}
// With indices tracking
function maxSubarrayWithIndices(arr) {
let maxSoFar = arr[0];
let maxEndingHere = arr[0];
let start = 0, end = 0, tempStart = 0;
for (let i = 1; i < arr.length; i++) {
if (arr[i] > maxEndingHere + arr[i]) {
maxEndingHere = arr[i];
tempStart = i; // New subarray starts here
} else {
maxEndingHere = maxEndingHere + arr[i];
}
if (maxEndingHere > maxSoFar) {
maxSoFar = maxEndingHere;
start = tempStart;
end = i;
}
}
return {sum: maxSoFar, start, end};
}
// Example
const arr = [-2, 1, -3, 4, -1, 2, 1, -5, 4];
console.log(maxSubarraySum(arr)); // 6 (subarray [4,-1,2,1])
console.log(maxSubarrayWithIndices(arr)); // {sum: 6, start: 3, end: 6}
Example: Maximum Product Subarray
function maxProductSubarray(arr) {
let maxProd = arr[0];
let currentMax = arr[0]; // Max product ending here
let currentMin = arr[0]; // Min product ending here
for (let i = 1; i < arr.length; i++) {
// Store currentMax before updating
const temp = currentMax;
// Current element could be:
// 1. Standalone (if prev products are small)
// 2. currentMax * arr[i] (if positive)
// 3. currentMin * arr[i] (if arr[i] negative)
currentMax = Math.max(
arr[i],
currentMax * arr[i],
currentMin * arr[i]
);
currentMin = Math.min(
arr[i],
temp * arr[i],
currentMin * arr[i]
);
maxProd = Math.max(maxProd, currentMax);
}
return maxProd;
}
// Example
console.log(maxProductSubarray([2, 3, -2, 4])); // 6
console.log(maxProductSubarray([-2, 0, -1])); // 0
Example: Subarray Sum Equals K
// Count subarrays with sum = k
function subarraySumK(arr, k) {
const prefixSumCount = new Map();
prefixSumCount.set(0, 1); // Empty subarray
let count = 0;
let currentSum = 0;
for (const num of arr) {
currentSum += num;
// If (currentSum - k) exists in map,
// we found subarray(s) with sum k
const target = currentSum - k;
if (prefixSumCount.has(target)) {
count += prefixSumCount.get(target);
}
// Update prefix sum count
prefixSumCount.set(
currentSum,
(prefixSumCount.get(currentSum) || 0) + 1
);
}
return count;
}
// Example
console.log(subarraySumK([1, 1, 1], 2)); // 2
console.log(subarraySumK([1, 2, 3], 3)); // 2
Array Key Techniques Summary
- Two Pointers: O(n) solution for pair finding, reversing, palindromes
- Sliding Window: Efficient for subarray/substring problems with constraints
- Prefix Sum: O(1) range queries after O(n) preprocessing
- Kadane's Algorithm: Maximum subarray sum in single pass O(n)
- Dutch Flag: Three-way partitioning for sorting limited distinct values
- Reversal: In-place array rotation with O(1) space
- Dynamic Arrays: Amortized O(1) insertion via geometric growth
- Sparse Arrays: Hash maps save space when array is mostly empty