Skip to content
beginnerPhase 1 · Foundation

Arrays

Understand contiguous memory storage, traversal, and fundamental array operations.

1h 30m
8 problems
Topic Progress0%

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

  1. Fixed Size: Once created, size cannot change (in Java)
  2. Zero-indexed: First element is at index 0
  3. Contiguous Memory: Elements are stored next to each other
  4. 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

  1. Empty Array
int[] arr = {};
arr.length == 0;  // true
// Accessing arr[0] throws ArrayIndexOutOfBoundsException
  1. Single Element
int[] arr = {5};
// Any two-pointer technique needs special handling
  1. Two Elements
int[] arr = {1, 2};
// Minimum case for many algorithms
  1. All Same Elements
int[] arr = {5, 5, 5, 5, 5};
// May cause issues with distinctness requirements
  1. Negative Numbers
int[] arr = {-3, -1, -4, -1, -5};
// Affects comparison logic
  1. Integer Overflow
int[] arr = {Integer.MAX_VALUE, 1};
// Sum may overflow
  1. 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:

  1. Can the array be empty?
  2. Can it have negative numbers?
  3. Can it have duplicate values?
  4. Is it sorted?
  5. What should I return if no solution exists?
  6. 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

  1. NullPointerException
int[] arr = null;
// arr.length -> NullPointerException
// Always check for null first
  1. ArrayIndexOutOfBoundsException
int[] arr = {1, 2, 3};
// arr[3] -> ArrayIndexOutOfBoundsException
// Valid indices: 0, 1, 2
  1. 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

0/6solved
Best Time to Buy and Sell Stock
Single Pass

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
Two Sum
Hash Map

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
Product of Array Except Self
Prefix Sum

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
Three Sum
Two Pointers + Sorting

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
Merge Intervals
Greedy - Interval Merge

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
Insert Interval
Interval Insertion

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?

Question 1 options

2. What is the time complexity of inserting at the beginning of an array?

Question 2 options

3. Which pattern is best for finding two numbers that add to a target?

Question 3 options

4. What is the primary purpose of Arrays?

Question 4 options

Flashcards

Question

What is the time complexity of array access by index?

Answer

O(1) - Constant time. Arrays use direct memory address calculation.

Question

When should you use a HashMap instead of nested loops?

Answer

When you need to find complement values or check existence in O(1) time.

Question

What is prefix sum used for?

Answer

Answering range sum queries in O(1) after O(n) preprocessing.

Question

What is Arrays?

Answer

Arrays is a key concept in software engineering.

Question

When to use Arrays?

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:

  1. Two Pointers - palindromes, sorted arrays
  2. Sliding Window - subarray problems
  3. Prefix Sum - range queries
  4. HashMap - frequency, complements
  5. Sorting - pair problems

Edge Cases:

  • Empty array
  • Single element
  • All same elements
  • Negative numbers
  • Integer overflow