Chapter 2: Arrays and Basic Operations
Introduction
Arrays are the most fundamental data structure in programming. They store collections of elements in contiguous memory locations, providing fast access by index. Understanding arrays deeply—how they're laid out in memory, their performance characteristics, and common operations—forms the foundation for all other data structures.
Why This Matters
Arrays appear everywhere in programming:
- Storing lists of data (students, products, scores)
- Implementing other data structures (stacks, queues, hash tables)
- Processing images (2D arrays of pixels)
- Building matrices for scientific computing
- Managing buffers in system programming
Mastering array operations and their complexity is essential because:
- They're the building block for most algorithms
- Understanding memory layout helps you write cache-friendly code
- Many interview questions test array manipulation skills
- Performance differences between operations can be huge
How to Study This Chapter
- Visualize memory layout - Draw arrays as boxes in memory
- Trace operations step by step - Follow pointer arithmetic
- Implement in all three languages - See language differences
- Measure performance - Time operations with different array sizes
- Practice variations - Modify examples to solve related problems
What is an Array?
An array is a collection of elements of the same type stored in contiguous memory locations. Each element can be accessed directly using an index.
Memory Layout
Array: [10, 20, 30, 40, 50]
Memory visualization:
Index: 0 1 2 3 4
+----+----+----+----+----+
Value: | 10 | 20 | 30 | 40 | 50 |
+----+----+----+----+----+
Address: 1000 1004 1008 1012 1016 (assuming 4-byte integers)
Key Properties:
- Elements stored consecutively in memory
- Constant-time access:
arr[i]is instant - Fixed size in C (dynamic in C++ vector, Java ArrayList)
- Index starts at 0 in C, C++, Java
Array Access Formula
element_address = base_address + (index × element_size)
Example: arr[3] in an int array starting at 1000:
address = 1000 + (3 × 4) = 1012
This is why array access is O(1)—it's a simple calculation.
Creating Arrays
C - Static Arrays
#include <stdio.h>
int main() {
// Declaration and initialization
int arr[5] = {10, 20, 30, 40, 50};
// Partial initialization (rest are 0)
int arr2[5] = {1, 2}; // {1, 2, 0, 0, 0}
// Let compiler determine size
int arr3[] = {5, 10, 15, 20}; // Size is 4
// Print array
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
C - Dynamic Arrays
#include <stdio.h>
#include <stdlib.h>
int main() {
int n = 5;
// Allocate array dynamically
int *arr = (int*)malloc(n * sizeof(int));
if (arr == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
// Initialize
for (int i = 0; i < n; i++) {
arr[i] = i * 10;
}
// Print
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// Free memory
free(arr);
return 0;
}
C++ - Static and Dynamic Arrays
#include <iostream>
#include <vector>
using namespace std;
int main() {
// Static array (C-style)
int arr1[5] = {10, 20, 30, 40, 50};
// Dynamic array using vector (recommended)
vector<int> arr2 = {10, 20, 30, 40, 50};
// Vector with size and default value
vector<int> arr3(5, 0); // 5 elements, all 0
// Add elements dynamically
vector<int> arr4;
arr4.push_back(10);
arr4.push_back(20);
arr4.push_back(30);
// Print using range-based for loop
for (int val : arr2) {
cout << val << " ";
}
cout << endl;
// Print with index
for (size_t i = 0; i < arr2.size(); i++) {
cout << arr2[i] << " ";
}
cout << endl;
return 0;
}
Java - Arrays and ArrayList
import java.util.ArrayList;
import java.util.Arrays;
public class ArrayDemo {
public static void main(String[] args) {
// Static array
int[] arr1 = {10, 20, 30, 40, 50};
// Array with specified size
int[] arr2 = new int[5];
for (int i = 0; i < arr2.length; i++) {
arr2[i] = i * 10;
}
// ArrayList (dynamic)
ArrayList<Integer> arr3 = new ArrayList<>();
arr3.add(10);
arr3.add(20);
arr3.add(30);
// Print array
System.out.println(Arrays.toString(arr1));
// Print ArrayList
System.out.println(arr3);
// Enhanced for loop
for (int val : arr1) {
System.out.print(val + " ");
}
System.out.println();
}
}
Basic Array Operations
1. Traversal - O(n)
Visit every element once.
C Example:
#include <stdio.h>
void traverseArray(int arr[], int size) {
printf("Array elements: ");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {5, 10, 15, 20, 25};
traverseArray(arr, 5);
return 0;
}
C++ Example:
#include <iostream>
#include <vector>
using namespace std;
void traverseArray(const vector<int>& arr) {
cout << "Array elements: ";
for (int val : arr) {
cout << val << " ";
}
cout << endl;
}
int main() {
vector<int> arr = {5, 10, 15, 20, 25};
traverseArray(arr);
return 0;
}
Java Example:
public class Traversal {
public static void traverseArray(int[] arr) {
System.out.print("Array elements: ");
for (int val : arr) {
System.out.print(val + " ");
}
System.out.println();
}
public static void main(String[] args) {
int[] arr = {5, 10, 15, 20, 25};
traverseArray(arr);
}
}
Complexity: O(n) time, O(1) space
2. Access by Index - O(1)
Directly access any element.
C Example:
int getElement(int arr[], int index) {
return arr[index];
}
void setElement(int arr[], int index, int value) {
arr[index] = value;
}
C++ Example:
int getElement(const vector<int>& arr, int index) {
return arr[index]; // or arr.at(index) for bounds checking
}
void setElement(vector<int>& arr, int index, int value) {
arr[index] = value;
}
Java Example:
public static int getElement(int[] arr, int index) {
return arr[index];
}
public static void setElement(int[] arr, int index, int value) {
arr[index] = value;
}
Complexity: O(1) time, O(1) space
3. Search - O(n) for unsorted, O(log n) for sorted
Linear Search (Unsorted Array):
C Example:
#include <stdio.h>
int linearSearch(int arr[], int size, int target) {
for (int i = 0; i < size; i++) {
if (arr[i] == target) {
return i; // Found at index i
}
}
return -1; // Not found
}
int main() {
int arr[] = {64, 25, 12, 22, 11};
int target = 12;
int result = linearSearch(arr, 5, target);
if (result != -1) {
printf("Found at index %d\n", result);
} else {
printf("Not found\n");
}
return 0;
}
C++ Example:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int linearSearch(const vector<int>& arr, int target) {
for (size_t i = 0; i < arr.size(); i++) {
if (arr[i] == target) {
return i;
}
}
return -1;
}
int main() {
vector<int> arr = {64, 25, 12, 22, 11};
int result = linearSearch(arr, 12);
if (result != -1) {
cout << "Found at index " << result << endl;
} else {
cout << "Not found" << endl;
}
return 0;
}
Java Example:
public class LinearSearch {
public static int linearSearch(int[] arr, int target) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) {
return i;
}
}
return -1;
}
public static void main(String[] args) {
int[] arr = {64, 25, 12, 22, 11};
int result = linearSearch(arr, 12);
if (result != -1) {
System.out.println("Found at index " + result);
} else {
System.out.println("Not found");
}
}
}
Complexity: O(n) time, O(1) space
4. Insertion
At the End - O(1) for dynamic arrays with capacity
C Example (Dynamic Array):
#include <stdio.h>
#include <stdlib.h>
int* insertAtEnd(int *arr, int *size, int *capacity, int value) {
if (*size == *capacity) {
// Resize array
*capacity *= 2;
arr = (int*)realloc(arr, (*capacity) * sizeof(int));
}
arr[*size] = value;
(*size)++;
return arr;
}
int main() {
int capacity = 4;
int size = 3;
int *arr = (int*)malloc(capacity * sizeof(int));
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
arr = insertAtEnd(arr, &size, &capacity, 40);
arr = insertAtEnd(arr, &size, &capacity, 50);
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
C++ Example:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> arr = {10, 20, 30};
arr.push_back(40); // O(1) amortized
arr.push_back(50);
for (int val : arr) {
cout << val << " ";
}
cout << endl;
return 0;
}
Java Example:
import java.util.ArrayList;
public class InsertAtEnd {
public static void main(String[] args) {
ArrayList<Integer> arr = new ArrayList<>();
arr.add(10);
arr.add(20);
arr.add(30);
arr.add(40); // O(1) amortized
System.out.println(arr);
}
}
At the Beginning - O(n) - Must shift all elements
C Example:
#include <stdio.h>
void insertAtBeginning(int arr[], int *size, int value) {
// Shift all elements to the right
for (int i = *size; i > 0; i--) {
arr[i] = arr[i - 1];
}
arr[0] = value;
(*size)++;
}
int main() {
int arr[10] = {20, 30, 40};
int size = 3;
insertAtBeginning(arr, &size, 10);
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n"); // Output: 10 20 30 40
return 0;
}
C++ Example:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> arr = {20, 30, 40};
arr.insert(arr.begin(), 10); // O(n)
for (int val : arr) {
cout << val << " ";
}
cout << endl; // Output: 10 20 30 40
return 0;
}
Java Example:
import java.util.ArrayList;
public class InsertAtBeginning {
public static void main(String[] args) {
ArrayList<Integer> arr = new ArrayList<>();
arr.add(20);
arr.add(30);
arr.add(40);
arr.add(0, 10); // O(n)
System.out.println(arr); // [10, 20, 30, 40]
}
}
Complexity: O(n) time, O(1) space
At Specific Position - O(n)
C Example:
#include <stdio.h>
void insertAtPosition(int arr[], int *size, int pos, int value) {
// Shift elements from pos to end
for (int i = *size; i > pos; i--) {
arr[i] = arr[i - 1];
}
arr[pos] = value;
(*size)++;
}
int main() {
int arr[10] = {10, 20, 40, 50};
int size = 4;
insertAtPosition(arr, &size, 2, 30); // Insert 30 at index 2
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n"); // Output: 10 20 30 40 50
return 0;
}
C++ Example:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> arr = {10, 20, 40, 50};
arr.insert(arr.begin() + 2, 30); // Insert at index 2
for (int val : arr) {
cout << val << " ";
}
cout << endl; // Output: 10 20 30 40 50
return 0;
}
Java Example:
import java.util.ArrayList;
public class InsertAtPosition {
public static void main(String[] args) {
ArrayList<Integer> arr = new ArrayList<>();
arr.add(10);
arr.add(20);
arr.add(40);
arr.add(50);
arr.add(2, 30); // Insert at index 2
System.out.println(arr); // [10, 20, 30, 40, 50]
}
}
Complexity: O(n) time, O(1) space
5. Deletion
Delete from End - O(1)
C Example:
void deleteFromEnd(int arr[], int *size) {
if (*size > 0) {
(*size)--;
}
}
C++ Example:
vector<int> arr = {10, 20, 30};
arr.pop_back(); // O(1)
Java Example:
ArrayList<Integer> arr = new ArrayList<>();
arr.remove(arr.size() - 1); // O(1)
Delete from Beginning - O(n)
C Example:
#include <stdio.h>
void deleteFromBeginning(int arr[], int *size) {
if (*size > 0) {
for (int i = 0; i < *size - 1; i++) {
arr[i] = arr[i + 1];
}
(*size)--;
}
}
int main() {
int arr[] = {10, 20, 30, 40};
int size = 4;
deleteFromBeginning(arr, &size);
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n"); // Output: 20 30 40
return 0;
}
C++ Example:
vector<int> arr = {10, 20, 30, 40};
arr.erase(arr.begin()); // O(n)
Java Example:
ArrayList<Integer> arr = new ArrayList<>();
arr.remove(0); // O(n)
Complexity: O(n) time
Delete at Position - O(n)
C Example:
void deleteAtPosition(int arr[], int *size, int pos) {
if (pos >= 0 && pos < *size) {
for (int i = pos; i < *size - 1; i++) {
arr[i] = arr[i + 1];
}
(*size)--;
}
}
6. Reverse Array - O(n)
C Example:
#include <stdio.h>
void reverseArray(int arr[], int size) {
int left = 0;
int right = size - 1;
while (left < right) {
// Swap
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
left++;
right--;
}
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int size = 5;
reverseArray(arr, size);
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n"); // Output: 5 4 3 2 1
return 0;
}
C++ Example:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> arr = {1, 2, 3, 4, 5};
reverse(arr.begin(), arr.end()); // STL function
for (int val : arr) {
cout << val << " ";
}
cout << endl; // Output: 5 4 3 2 1
return 0;
}
Java Example:
import java.util.Collections;
import java.util.ArrayList;
import java.util.Arrays;
public class ReverseArray {
public static void reverseArray(int[] arr) {
int left = 0;
int right = arr.length - 1;
while (left < right) {
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
left++;
right--;
}
}
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
reverseArray(arr);
System.out.println(Arrays.toString(arr)); // [5, 4, 3, 2, 1]
}
}
Complexity: O(n) time, O(1) space
7. Find Maximum/Minimum - O(n)
C Example:
#include <stdio.h>
int findMax(int arr[], int size) {
int max = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
int findMin(int arr[], int size) {
int min = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] < min) {
min = arr[i];
}
}
return min;
}
int main() {
int arr[] = {12, 35, 1, 10, 34, 1};
printf("Max: %d\n", findMax(arr, 6));
printf("Min: %d\n", findMin(arr, 6));
return 0;
}
C++ Example:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> arr = {12, 35, 1, 10, 34, 1};
int maxVal = *max_element(arr.begin(), arr.end());
int minVal = *min_element(arr.begin(), arr.end());
cout << "Max: " << maxVal << endl;
cout << "Min: " << minVal << endl;
return 0;
}
Java Example:
import java.util.Arrays;
public class FindMinMax {
public static void main(String[] args) {
int[] arr = {12, 35, 1, 10, 34, 1};
int max = Arrays.stream(arr).max().getAsInt();
int min = Arrays.stream(arr).min().getAsInt();
System.out.println("Max: " + max);
System.out.println("Min: " + min);
}
}
Multi-Dimensional Arrays
2D Arrays (Matrices)
C Example:
#include <stdio.h>
int main() {
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// Print matrix
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
return 0;
}
C++ Example:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<vector<int>> matrix = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
for (const auto& row : matrix) {
for (int val : row) {
cout << val << " ";
}
cout << endl;
}
return 0;
}
Java Example:
public class Matrix {
public static void main(String[] args) {
int[][] matrix = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
for (int[] row : matrix) {
for (int val : row) {
System.out.print(val + " ");
}
System.out.println();
}
}
}
Memory Layout of 2D Arrays
In C (row-major order):
int matrix[2][3] = {{1,2,3}, {4,5,6}};
Memory: [1][2][3][4][5][6]
└─ row 0 ─┘└─ row 1 ─┘
Common Array Patterns
Two Pointers Technique
Remove Duplicates from Sorted Array:
#include <stdio.h>
int removeDuplicates(int arr[], int size) {
if (size == 0) return 0;
int writeIndex = 1;
for (int i = 1; i < size; i++) {
if (arr[i] != arr[i - 1]) {
arr[writeIndex] = arr[i];
writeIndex++;
}
}
return writeIndex; // New size
}
int main() {
int arr[] = {1, 1, 2, 2, 3, 4, 4, 5};
int size = 8;
int newSize = removeDuplicates(arr, size);
for (int i = 0; i < newSize; i++) {
printf("%d ", arr[i]);
}
printf("\n"); // Output: 1 2 3 4 5
return 0;
}
Sliding Window
Find Maximum Sum Subarray of Size K:
#include <stdio.h>
int maxSumSubarray(int arr[], int size, int k) {
if (size < k) return -1;
// Compute sum of first window
int windowSum = 0;
for (int i = 0; i < k; i++) {
windowSum += arr[i];
}
int maxSum = windowSum;
// Slide window
for (int i = k; i < size; i++) {
windowSum = windowSum - arr[i - k] + arr[i];
if (windowSum > maxSum) {
maxSum = windowSum;
}
}
return maxSum;
}
int main() {
int arr[] = {1, 4, 2, 10, 23, 3, 1, 0, 20};
int k = 4;
printf("Max sum: %d\n", maxSumSubarray(arr, 9, k));
return 0;
}
Complexity Summary
| Operation | Time Complexity | Space Complexity |
|---|---|---|
| Access by index | O(1) | O(1) |
| Search (unsorted) | O(n) | O(1) |
| Search (sorted) | O(log n) | O(1) |
| Insert at end | O(1) amortized | O(1) |
| Insert at beginning | O(n) | O(1) |
| Insert at position | O(n) | O(1) |
| Delete from end | O(1) | O(1) |
| Delete from beginning | O(n) | O(1) |
| Delete from position | O(n) | O(1) |
| Traverse | O(n) | O(1) |
| Reverse | O(n) | O(1) |
| Find min/max | O(n) | O(1) |
Common Mistakes
- Off-by-one errors - Accessing
arr[size]instead ofarr[size-1] - Buffer overflow - Writing beyond array bounds in C
- Not checking bounds - Causes crashes or security vulnerabilities
- Memory leaks in C - Forgetting to
free()dynamic arrays - Inefficient insertion/deletion - Not realizing O(n) cost
- Wrong initial value - Starting max search with 0 instead of
arr[0] - Integer overflow - Sum of large numbers exceeds int range
- Modifying array during iteration - Can skip elements
Debugging Tips
- Print arrays - Visualize state at each step
- Check bounds - Verify index is within [0, size-1]
- Use debuggers - Step through with gdb, Visual Studio, IntelliJ
- Test edge cases - Empty array, single element, all same values
- Valgrind for C - Detect memory errors
- AddressSanitizer - Compiler flag for memory safety
- Draw diagrams - Sketch array state on paper
- Start simple - Test with small arrays first
Mini Exercises
- Write a function to find the second largest element in an array
- Rotate an array to the right by k positions
- Check if an array is sorted
- Merge two sorted arrays into one sorted array
- Find all pairs that sum to a target value
- Remove all occurrences of a value from an array
- Move all zeros to the end while maintaining order
- Find the missing number in an array of 1 to n
- Implement array rotation using reversal algorithm
- Find the intersection of two arrays
Review Questions
- Why is array access O(1)? Explain the formula.
- What's the difference between static and dynamic arrays?
- Why is inserting at the beginning of an array O(n)?
- How does memory layout affect cache performance?
- When should you use an array vs. a linked list?
Reference Checklist
By the end of this chapter, you should be able to:
- Create arrays in C, C++, and Java
- Understand array memory layout
- Implement traversal, search, insert, delete operations
- Analyze time and space complexity of array operations
- Use two-pointer and sliding window techniques
- Work with multi-dimensional arrays
- Recognize common array pitfalls
- Choose appropriate array type for the problem
Next Steps
Now that you understand arrays and basic operations, Chapter 3 explores searching algorithms in depth. You'll learn linear search, binary search, interpolation search, and when to use each approach for maximum efficiency.
Key Takeaway: Arrays are simple but powerful. Their contiguous memory layout enables O(1) access but makes insertion/deletion expensive. Understanding these tradeoffs is crucial for choosing the right data structure for your problem.