What is an Array
An array is a contiguous collection of elements stored in memory. Think of it as a row of boxes, each holding one value.
Memory Layout
Array arr = [10, 20, 30, 40, 50]
Memory Address: 1000 1004 1008 1012 1016
+-----+-----+-----+-----+-----+
| 10 | 20 | 30 | 40 | 50 |
+-----+-----+-----+-----+-----+
Index: 0 1 2 3 4
Key Properties
- Fixed Size: Once created, size cannot change (in Java)
- Zero-indexed: First element is at index 0
- Contiguous Memory: Elements are stored next to each other
- Random Access: Can access any element in O(1) time
Why Arrays Matter
Arrays are the foundation of almost every data structure:
- Strings are arrays of characters
- Stacks can be implemented with arrays
- Heaps are stored as arrays
- Hash Maps use arrays internally
Java Array Declaration
// Declaration and initialization
int[] nums = new int[5]; // Array of 5 zeros
int[] nums = {1, 2, 3, 4, 5}; // Array with values
int[] nums = new int[]{1, 2, 3}; // Alternative syntax
// Accessing elements
int first = nums[0]; // 1
int third = nums[2]; // 3
// Modifying elements
nums[1] = 10; // Array is now [1, 10, 3, 4, 5]
// Array length
int len = nums.length; // 5
Array Operations
Accessing Elements
// Access by index - O(1)
int value = arr[index];
// Why O(1)?
// Address = base_address + index × element_size
// arr[3] = 1000 + 3 × 4 = 1012 (for int array)
Traversal
// Forward traversal - O(n)
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
// Enhanced for loop - O(n)
for (int num : arr) {
System.out.println(num);
}
// Backward traversal - O(n)
for (int i = arr.length - 1; i >= 0; i--) {
System.out.println(arr[i]);
}
Insertion
// Insert at end - O(1) if space available
// Insert at beginning - O(n) - must shift all elements
// Insert at middle - O(n) - must shift half elements
// Example: Insert 25 at index 2
// Before: [10, 20, 30, 40, 50]
// After: [10, 20, 25, 30, 40, 50]
// Shift elements right
Deletion
// Delete from end - O(1)
// Delete from beginning - O(n) - must shift all elements
// Delete from middle - O(n) - must shift elements
// Example: Delete element at index 2
// Before: [10, 20, 30, 40, 50]
// After: [10, 20, 40, 50]
// Shift elements left
Search
// Linear Search - O(n)
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) return i;
}
return -1;
// Binary Search (sorted array) - O(log n)
int low = 0, high = arr.length - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (arr[mid] == target) return mid;
else if (arr[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1;
Complexity Summary
| Operation | Time | Space |
|---|---|---|
| Access | O(1) | O(1) |
| Search (unsorted) | O(n) | O(1) |
| Search (sorted) | O(log n) | O(1) |
| Insert at end | O(1) | O(1) |
| Insert at beginning | O(n) | O(n) |
| Delete at end | O(1) | O(1) |
| Delete at beginning | O(n) | O(n) |
Array Patterns
Pattern 1: Two Pointers
Use when you need to compare elements from different positions.
// Check if array is palindrome
boolean isPalindrome(int[] arr) {
int left = 0, right = arr.length - 1;
while (left < right) {
if (arr[left] != arr[right]) return false;
left++;
right--;
}
return true;
}
Pattern 2: Sliding Window
Use when you need to find subarray with certain properties.
// Find max sum of k consecutive elements
int maxSum(int[] arr, int k) {
int windowSum = 0;
for (int i = 0; i < k; i++) {
windowSum += arr[i];
}
int maxSum = windowSum;
for (int i = k; i < arr.length; i++) {
windowSum += arr[i] - arr[i - k];
maxSum = Math.max(maxSum, windowSum);
}
return maxSum;
}
Pattern 3: Prefix Sum
Use when you need to answer range sum queries.
// Build prefix sum array
int[] prefix = new int[n + 1];
for (int i = 0; i < n; i++) {
prefix[i + 1] = prefix[i] + arr[i];
}
// Query sum from index i to j
int rangeSum = prefix[j + 1] - prefix[i];
Pattern 4: Sorting First
Use when order doesn't matter.
// Find pair with given sum
Arrays.sort(arr);
int left = 0, right = arr.length - 1;
while (left < right) {
int sum = arr[left] + arr[right];
if (sum == target) return true;
else if (sum < target) left++;
else right--;
}
Pattern 5: HashMap
Use when you need fast lookup.
// Find two numbers that add to target
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < arr.length; i++) {
int complement = target - arr[i];
if (map.containsKey(complement)) {
return new int[] {map.get(complement), i};
}
map.put(arr[i], i);
}
Edge Cases
Common Edge Cases
- Empty Array
int[] arr = {};
arr.length == 0; // true
// Accessing arr[0] throws ArrayIndexOutOfBoundsException
- Single Element
int[] arr = {5};
// Any two-pointer technique needs special handling
- Two Elements
int[] arr = {1, 2};
// Minimum case for many algorithms
- All Same Elements
int[] arr = {5, 5, 5, 5, 5};
// May cause issues with distinctness requirements
- Negative Numbers
int[] arr = {-3, -1, -4, -1, -5};
// Affects comparison logic
- Integer Overflow
int[] arr = {Integer.MAX_VALUE, 1};
// Sum may overflow
- Sorted vs Unsorted
int[] sorted = {1, 2, 3, 4, 5};
int[] unsorted = {3, 1, 4, 1, 5};
// Different approaches may be needed
How to Handle Edge Cases
Always check before processing:
public int solve(int[] arr) {
// Handle edge cases first
if (arr == null || arr.length == 0) {
return 0; // or throw exception
}
if (arr.length == 1) {
return arr[0]; // or special handling
}
// Main logic here
// ...
}
Interview Checklist
Before coding, ask yourself:
- Can the array be empty?
- Can it have negative numbers?
- Can it have duplicate values?
- Is it sorted?
- What should I return if no solution exists?
- Can the result overflow?
Java Array API
Arrays Utility Class
import java.util.Arrays;
int[] arr = {5, 2, 8, 1, 9};
// Sorting
Arrays.sort(arr); // [1, 2, 5, 8, 9]
// Searching (binary search, array must be sorted)
int index = Arrays.binarySearch(arr, 5); // 2
// Filling
Arrays.fill(arr, 0); // [0, 0, 0, 0, 0]
// Copying
int[] copy = Arrays.copyOf(arr, 3); // [0, 0, 0]
int[] copy2 = Arrays.copyOfRange(arr, 1, 3); // [0, 0]
// Comparing
boolean equal = Arrays.equals(arr, copy); // false
// String representation
String str = Arrays.toString(arr); // "[0, 0, 0, 0, 0]"
// 2D array
int[][] grid = {{1, 2}, {3, 4}};
System.out.println(Arrays.deepToString(grid)); // "[[1, 2], [3, 4]]"
ArrayList (Dynamic Array)
import java.util.ArrayList;
ArrayList<Integer> list = new ArrayList<>();
// Adding elements
list.add(1); // [1]
list.add(2); // [1, 2]
list.add(0, 3); // [3, 1, 2] - insert at index 0
// Accessing
int val = list.get(0); // 3
// Modifying
list.set(0, 10); // [10, 1, 2]
// Removing
list.remove(0); // [1, 2] - remove by index
list.remove(Integer.valueOf(1)); // [2] - remove by value
// Size
int size = list.size(); // 2
// Searching
boolean has = list.contains(2); // true
int idx = list.indexOf(2); // 0
// Converting
int[] arr = list.stream().mapToInt(i -> i).toArray();
Common Mistakes with Java Arrays
- NullPointerException
int[] arr = null;
// arr.length -> NullPointerException
// Always check for null first
- ArrayIndexOutOfBoundsException
int[] arr = {1, 2, 3};
// arr[3] -> ArrayIndexOutOfBoundsException
// Valid indices: 0, 1, 2
- Comparing Arrays with ==
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
a == b; // false (compares references)
a.equals(b); // false (same as ==)
Arrays.equals(a, b); // true (compares contents)
Practice Problems
You are given an array prices where prices[i] is the price of a given stock on the ith day. You want to maximize your profit by choosing a single day to buy and a single day to sell in the future.
Example:
Input: prices = [7,1,5,3,6,4]
Output: 5
Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5.
Brute Force Solution — O(n²) time, O(1) space
Check every pair of buy/sell days
class Solution {
public int maxProfit(int[] prices) {
int maxProfit = 0;
for (int i = 0; i < prices.length; i++) {
for (int j = i + 1; j < prices.length; j++) {
maxProfit = Math.max(maxProfit, prices[j] - prices[i]);
}
}
return maxProfit;
}
}Optimal Solution — O(n) time, O(1) space
Track minimum price and maximum profit
class Solution {
public int maxProfit(int[] prices) {
int minPrice = Integer.MAX_VALUE;
int maxProfit = 0;
for (int price : prices) {
minPrice = Math.min(minPrice, price);
maxProfit = Math.max(maxProfit, price - minPrice);
}
return maxProfit;
}
}Edge Cases:
- Prices always decreasing
- Single price
- All same prices
- Two prices only
Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.
Example:
Input: nums = [2,7,11,15], target = 9
Output: [0,1]
Because nums[0] + nums[1] == 9, we return [0, 1].
Brute Force Solution — O(n²) time, O(1) space
Check every pair
class Solution {
public int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
return new int[] {i, j};
}
}
}
return new int[] {};
}
}Optimal Solution — O(n) time, O(n) space
HashMap for O(1) lookup
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[] {map.get(complement), i};
}
map.put(nums[i], i);
}
return new int[] {};
}
}Edge Cases:
- No solution exists
- Multiple solutions
- Negative numbers
- Same element used twice
Given an integer array nums, return an array answer such that answer[i] is equal to the product of all the elements of nums except nums[i].
Example:
Input: nums = [1,2,3,4]
Output: [24,12,8,6]
answer[0] = 2×3×4 = 24, answer[1] = 1×3×4 = 12, etc.
Brute Force Solution — O(n²) time, O(1) space
For each element, multiply all others
class Solution {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] answer = new int[n];
for (int i = 0; i < n; i++) {
answer[i] = 1;
for (int j = 0; j < n; j++) {
if (i != j) answer[i] *= nums[j];
}
}
return answer;
}
}Optimal Solution — O(n) time, O(1) space
Prefix and suffix products
class Solution {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] answer = new int[n];
// Prefix products
answer[0] = 1;
for (int i = 1; i < n; i++) {
answer[i] = answer[i - 1] * nums[i - 1];
}
// Suffix products
int suffix = 1;
for (int i = n - 1; i >= 0; i--) {
answer[i] *= suffix;
suffix *= nums[i];
}
return answer;
}
}Edge Cases:
- Contains zero
- Contains negative numbers
- All ones
- Single element
Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.
Example:
Input: nums = [-1,0,1,2,-1,-4]
Output: [[-1,-1,2],[-1,0,1]]
The distinct triplets that sum to zero are [-1,0,1] and [-1,-1,2].
Brute Force Solution — O(n³) time, O(1) space
Check all triplets with three nested loops
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
for (int i = 0; i < nums.length - 2; i++) {
for (int j = i + 1; j < nums.length - 1; j++) {
for (int k = j + 1; k < nums.length; k++) {
if (nums[i] + nums[j] + nums[k] == 0) {
List<Integer> triplet = Arrays.asList(nums[i], nums[j], nums[k]);
Collections.sort(triplet);
if (!result.contains(triplet)) result.add(triplet);
}
}
}
}
return result;
}
}Optimal Solution — O(n²) time, O(1) excluding output space
Sort array, fix one element, use two pointers for remaining two
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue; // Skip duplicates
int left = i + 1, right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
while (left < right && nums[left] == nums[left + 1]) left++; // Skip duplicates
while (left < right && nums[right] == nums[right - 1]) right--; // Skip duplicates
left++;
right--;
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return result;
}
}Edge Cases:
- Less than 3 elements
- All zeros
- No solution exists
- Duplicate triplets
Given an array of intervals where intervals[i] = [starti, endi], merge all overlapping intervals, and return an array of the non-overlapping intervals.
Example:
Input: intervals = [[1,3],[2,6],[8,10],[15,18]]
Output: [[1,6],[8,10],[15,18]]
Since intervals [1,3] and [2,6] overlap, merge them into [1,6].
Optimal Solution — O(n log n) time, O(n) space
Sort by start time, merge overlapping intervals
class Solution {
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
List<int[]> merged = new ArrayList<>();
for (int[] interval : intervals) {
if (merged.isEmpty() || merged.get(merged.size() - 1)[1] < interval[0]) {
merged.add(interval);
} else {
merged.get(merged.size() - 1)[1] = Math.max(
merged.get(merged.size() - 1)[1], interval[1]);
}
}
return merged.toArray(new int[0][]);
}
}Edge Cases:
- No overlapping intervals
- All intervals overlap
- Single interval
- Intervals touching at endpoints
You are given an array of non-overlapping intervals sorted by their start times. Insert a new interval into the intervals (if necessary) and merge all necessary intervals.
Example:
Input: intervals = [[1,3],[6,9]], newInterval = [2,5]
Output: [[1,5],[6,9]]
Insert [2,5] and merge with [1,3] to get [1,5].
Optimal Solution — O(n) time, O(n) space
Three-phase: add before, merge overlapping, add after
class Solution {
public int[][] insert(int[][] intervals, int[] newInterval) {
List<int[]> result = new ArrayList<>();
int i = 0;
// Add all intervals before newInterval
while (i < intervals.length && intervals[i][1] < newInterval[0]) {
result.add(intervals[i++]);
}
// Merge overlapping intervals
while (i < intervals.length && intervals[i][0] <= newInterval[1]) {
newInterval[0] = Math.min(newInterval[0], intervals[i][0]);
newInterval[1] = Math.max(newInterval[1], intervals[i][1]);
i++;
}
result.add(newInterval);
// Add remaining intervals
while (i < intervals.length) {
result.add(intervals[i++]);
}
return result.toArray(new int[0][]);
}
}Edge Cases:
- No intervals to merge (newInterval at end)
- All intervals merge into one
- Empty intervals array
- newInterval doesn't overlap with any
Quiz
1. What is the time complexity of accessing an element in an array by index?
2. What is the time complexity of inserting at the beginning of an array?
3. Which pattern is best for finding two numbers that add to a target?
4. What is the primary purpose of Arrays?
Flashcards
Question
What is the time complexity of array access by index?
Click to reveal answer
Answer
O(1) - Constant time. Arrays use direct memory address calculation.
Question
When should you use a HashMap instead of nested loops?
Click to reveal answer
Answer
When you need to find complement values or check existence in O(1) time.
Question
What is prefix sum used for?
Click to reveal answer
Answer
Answering range sum queries in O(1) after O(n) preprocessing.
Question
What is Arrays?
Click to reveal answer
Answer
Arrays is a key concept in software engineering.
Question
When to use Arrays?
Click to reveal answer
Answer
Use Arrays when building production systems that require reliability, scalability, and maintainability.
Revision Notes
Key Takeaways
- 1.Arrays provide O(1) access but O(n) insertion/deletion
- 2.Contiguous memory enables cache-friendly operations
- 3.Use HashMap for O(1) lookup when order doesn't matter
- 4.Prefix sum enables O(1) range queries
- 5.Always check edge cases before coding
Interview Tips
- •Clarify if array is sorted before suggesting binary search
- •Ask about duplicate values
- •Discuss time-space tradeoffs
- •Handle edge cases explicitly
Cheat Sheet
Arrays Cheat Sheet
Key Operations:
| Operation | Time |
|---|---|
| Access | O(1) |
| Search | O(n) |
| Insert (end) | O(1) |
| Insert (beginning) | O(n) |
Common Patterns:
- Two Pointers - palindromes, sorted arrays
- Sliding Window - subarray problems
- Prefix Sum - range queries
- HashMap - frequency, complements
- Sorting - pair problems
Edge Cases:
- Empty array
- Single element
- All same elements
- Negative numbers
- Integer overflow