4.10 - Implementing ArrayList Algorithms

superdancer16

Introduction

Topic 4.10 applies traversal skills to implement standard algorithms that solve common ArrayList problems. These algorithms mirror the array algorithms from Topic 4.5 but adapt to ArrayList's dynamic nature and method-based interface.

This topic is critical for the AP exam—particularly for the ArrayList free-response question (Question 3). You'll learn algorithms for finding extremes, computing statistics, checking properties, working with consecutive pairs, detecting duplicates, shifting, rotating, reversing, and modifying ArrayList structure. Additionally, we'll cover simultaneous traversal of multiple collections.

Finding Minimum and Maximum

Algorithm: Track the extreme value while traversing.

public static int findMax(ArrayList<Integer> list) 
{
    int max = list.get(0);
    for (int i = 1; i < list.size(); i++) 
    {
        if (list.get(i) > max) 
        {
            max = list.get(i);
        }
    }
    return max;
}

For objects: Find object with maximum attribute value.

public static Student findHighestGPA(ArrayList<Student> students) 
{
    Student best = students.get(0);
    for (int i = 1; i < students.size(); i++) 
        {
        if (students.get(i).getGPA() > best.getGPA()) 
        {
            best = students.get(i);
        }
    }
    return best;
}

Computing Sum and Average

public static double average(ArrayList<Integer> nums) 
{
    int sum = 0;
    for (int num : nums) 
    {
        sum += num;
    }
    return (double) sum / nums.size();
}

The average formula:

Checking Properties

At Least One Element

Return true immediately when found:

public static boolean hasNegative(ArrayList<Integer> nums) 
{
    for (int num : nums) 
    {
        if (num < 0) 
        {
            return true;
        }
    }
    return false;
}

All Elements

Return false immediately when violated:

public static boolean allPositive(ArrayList<Integer> nums) 
{
    for (int num : nums) 
    {
        if (num <= 0) 
        {
            return false;
        }
    }
    return true;
}

Count Elements

public static int countEvens(ArrayList<Integer> nums) 
{
    int count = 0;
    for (int num : nums) 
    {
        if (num % 2 == 0) 
        {
            count++;
        }
    }
    return count;
}

Accessing Consecutive Pairs

Pattern: Loop from 0 to size() - 2 to access pairs (list.get(i), list.get(i+1)).

public static int countIncreases(ArrayList<Integer> nums) 
{
    int count = 0;
    for (int i = 0; i < nums.size() - 1; i++) 
    {
        if (nums.get(i + 1) > nums.get(i)) 
        {
            count++;
        }
    }
    return count;
}

Critical: Must use size() - 1 to avoid IndexOutOfBoundsException.

Detecting Duplicates

Nested loop pattern:

public static boolean hasDuplicates(ArrayList<Integer> list) 
{
    for (int i = 0; i < list.size(); i++) 
    {
        for (int j = i + 1; j < list.size(); j++) 
        {
            if (list.get(i).equals(list.get(j))) 
            {
                return true;
            }
        }
    }
    return false;
}

Note: Use .equals() for object comparison, not ==.

Shifting Elements

Unlike arrays, ArrayLists can actually shrink and grow.

Shift left (remove first):

public static void shiftLeft(ArrayList<Integer> list) 
{
    if (list.size() > 0) 
    {
        list.remove(0);
    }
}
// [10, 20, 30] becomes [20, 30]

Shift right (add at beginning):

public static void shiftRight(ArrayList<Integer> list, int value) 
{
    list.add(0, value);
}
// Adding 5 to [10, 20, 30] gives [5, 10, 20, 30]

Rotating Elements

Rotate left (first wraps to end):

public static void rotateLeft(ArrayList<Integer> list) 
{
    if (list.size() > 0) 
    {
        int first = list.remove(0);
        list.add(first);
    }
}
// [10, 20, 30] becomes [20, 30, 10]

Rotate right (last wraps to start):

public static void rotateRight(ArrayList<Integer> list) 
{
    if (list.size() > 0) 
    {
        int last = list.remove(list.size() - 1);
        list.add(0, last);
    }
}
// [10, 20, 30] becomes [30, 10, 20]

Reversing Elements

In-place reversal:

public static void reverse(ArrayList<Integer> list) 
{
    for (int i = 0; i < list.size() / 2; i++) 
    {
        int temp = list.get(i);
        list.set(i, list.get(list.size() - 1 - i));
        list.set(list.size() - 1 - i, temp);
    }
}
// [1, 2, 3, 4, 5] becomes [5, 4, 3, 2, 1]

Inserting Elements

Pattern: Adjust index after insertion to avoid infinite loops.

public static void insertZerosAfterEvens(ArrayList<Integer> list) 
{
    for (int i = 0; i < list.size(); i++) 
    {
        if (list.get(i) % 2 == 0) 
        {
            list.add(i + 1, 0);
            i++;  // Skip the inserted 0
        }
    }
}
// [2, 3, 4] becomes [2, 0, 3, 4, 0]

Deleting Elements

Use backwards traversal:

public static void removeNegatives(ArrayList<Integer> list) 
{
    for (int i = list.size() - 1; i >= 0; i--) 
    {
        if (list.get(i) < 0) 
        {
            list.remove(i);
        }
    }
}

Why backwards? Removing elements causes shifts. Traversing backwards means shifts only affect already-processed elements.

Simultaneous Traversals

Some algorithms require multiple String, array, or ArrayList objects to be traversed simultaneously.

Element-wise Addition

public static ArrayList<Integer> addLists(ArrayList<Integer> a, 
                                           ArrayList<Integer> b) 
{
    ArrayList<Integer> result = new ArrayList<Integer>();
    int minSize = Math.min(a.size(), b.size());
    
    for (int i = 0; i < minSize; i++) 
    {
        result.add(a.get(i) + b.get(i));
    }
    return result;
}

Pattern: Use Math.min() to avoid index errors when sizes differ.

Comparing Two Lists

public static boolean listsEqual(ArrayList<Integer> a, 
                                  ArrayList<Integer> b) 
{
    if (a.size() != b.size()) 
    {
        return false;
    }
    
    for (int i = 0; i < a.size(); i++) 
    {
        if (!a.get(i).equals(b.get(i))) 
        {
            return false;
        }
    }
    return true;
}

Merging Sorted Lists

public static ArrayList<Integer> merge(ArrayList<Integer> a,
                                        ArrayList<Integer> b) 
{
    ArrayList<Integer> result = new ArrayList<Integer>();
    int i = 0, j = 0;
    
    while (i < a.size() && j < b.size()) 
    {
        if (a.get(i) <= b.get(j)) 
        {
            result.add(a.get(i));
            i++;
        } 
        else 
        {
            result.add(b.get(j));
            j++;
        }
    }
    
    // Add remaining elements
    while (i < a.size()) 
    {
        result.add(a.get(i));
        i++;
    }
    while (j < b.size()) 
    {
        result.add(b.get(j));
        j++;
    }
    
    return result;
}

Complete Example: Filter and Transform

// Keep only even numbers and double them
public static ArrayList<Integer> processEvens(ArrayList<Integer> nums) 
{
    ArrayList<Integer> result = new ArrayList<Integer>();
    
    for (int num : nums) 
    {
        if (num % 2 == 0) 
        {
            result.add(num * 2);
        }
    }
    
    return result;
}
// [1, 2, 3, 4, 5, 6] becomes [4, 8, 12]

Common Mistakes

Mistake 1: Off-by-one for consecutive pairs

// WRONG - accesses size()

for (int i = 0; i < list.size(); i++) {

    if (list.get(i) > list.get(i + 1)) { }

}

// WRONG - accesses size()
for (int i = 0; i < list.size(); i++) 
{
    if (list.get(i) > list.get(i + 1)) 
    { }
}

Mistake 2: Removing during enhanced for loop

// WRONG - ConcurrentModificationException
for (Integer num : list) 
{
    if (num < 0)
    {
      list.remove(num);
    }
}

Mistake 3: Not adjusting index after insertion

// WRONG - infinite loop possible
for (int i = 0; i < list.size(); i++) 
{
    list.add(i, 0);  // Missing i++ to skip inserted element
}

Practice Problems