Free for students · Ad-free · WCAG 2.1 AA Compliant · Accessibility

Implementing ArrayList Algorithms

Lesson ~11 min read 8 MCQs

In simple terms: In simple terms, this topic is about writing common recipes (algorithms) to inspect and change data inside an ArrayList, like finding the highest score or removing items from a list.

Why this matters

Imagine you're in charge of the ultimate road trip playlist for a drive from Chicago to Seattle. You've got all your songs in an ArrayList. But a good playlist isn't static—it needs managing. You might want to find the longest song for that boring stretch through South Dakota. Or maybe you need to calculate the average song length to see if you have enough music. What if your friend adds a bunch of songs you dislike? You'll need a way to go through and remove them. And to prevent repeats, you'll want to check for duplicate tracks.

These tasks—finding a max, calculating an average, removing items, checking for duplicates—are the bread and butter of programming. They are standard algorithms, and learning to implement them for ArrayLists is a fundamental skill. In this lesson, we'll break down the code patterns for these common tasks so you can manage any list of data like a pro.

Concept overview

flowchart TD
    A[Start: removeFailing(scores)] --> B{scores.size() > 0?};
    B -- No --> C[End];
    B -- Yes --> D[Set i = scores.size() - 1];
    D --> E{i >= 0?};
    E -- Yes --> F{scores.get(i) < 60?};
    F -- Yes --> G[scores.remove(i)];
    G --> H[Set i = i - 1];
    F -- No --> H;
    H --> E;
    E -- No --> C;
This diagram shows a flowchart for an algorithm that removes failing scores from a list. It starts by initializing a loop counter 'i' to the last index of the list and proceeds backwards, checking if the score at index 'i' is less than 60. If it is, the element is removed before decrementing 'i' to continue the loop.
A traversal means visiting each element in an ArrayList, like checking lockers.

Core explanation

Alright, let's dive into the common recipes, or algorithms, you'll use with ArrayLists. Almost all of them start with the same basic idea: a traversal. A traversal is just a fancy word for visiting every element in the list, usually with a for loop or a for-each loop.

Think of it like walking down a line of lockers. You start at the first one and check each one until you reach the end. What you do at each locker is what defines the algorithm.

"Read-Only" Algorithms: Just Looking

These algorithms inspect the data in the ArrayList without changing it.

Finding a Minimum or Maximum Value

Let's say you have an ArrayList of high scores from an arcade game. How do you find the highest one?

You can't just assume the max score is 0. What if all the scores are negative? (Tough game!) The safest way is to assume the first element is the maximum, and then challenge that assumption as you loop through the rest.

Tracing `findMax` to locate the highest score in an ArrayList.
// Finds the highest score in a list of integers
public int findMax(ArrayList<Integer> scores) {
    // 1. Assume the first score is the highest
    int maxScore = scores.get(0);

    // 2. Loop through the rest of the list
    for (int i = 1; i < scores.size(); i++) {
        // 3. If we find a higher score, update our max
        if (scores.get(i) > maxScore) {
            maxScore = scores.get(i);
        }
    }
    return maxScore;
}

Finding the minimum is the exact same logic, just with the > flipped to a <.

Computing a Sum or Average

To get the sum, you initialize a counter to zero, loop through the list, and add each element's value to your counter.

To get the average, you first find the sum, then divide by the total number of elements (list.size()).

Avoiding integer division pitfalls when calculating averages.
// Calculates the average of a list of numbers
public double calculateAverage(ArrayList<Integer> nums) {
    double sum = 0.0; // Use a double for the sum to avoid integer division later
    for (int num : nums) {
        sum += num;
    }

    if (nums.size() == 0) {
        return 0.0; // Avoid dividing by zero!
    }

    return sum / nums.size(); // double / int = double
}

Checking for Properties

Sometimes you just need a yes/no answer about the list.

  • Does at least one element have a property?
    (e.g., "Is there at least one failing grade?")
    • Start with a boolean flag set to false.
    • Loop through the list. If you find an element that meets the condition, set the flag to true and you can break out of the loop immediately. There's no need to keep looking.
  • Do all elements have a property?
    (e.g., "Are all temperatures above freezing?")
    • This one is a bit counter-intuitive. Start by assuming the answer is true.
    • Loop through the list looking for a counterexample—any one element that fails the condition.
    • If you find one, you can immediately set your flag to false and break.

"Read-Write" Algorithms: Changing the List

These algorithms modify the ArrayList by adding, removing, or reordering elements. This is where things get tricky.

An ArrayList is like a tightly packed line of people. If someone in the middle leaves, everyone behind them has to take one step forward to fill the gap. This changes their position, or index.

Deleting Elements

If you loop forwards while removing elements, you'll skip items! Imagine you remove the element at index 3. The element that was at index 4 immediately shuffles into index 3. But your loop counter moves on to i = 4, completely skipping the element that just moved.

The solution is to traverse backwards.

// Removes all scores below 60 from a list
public void removeFailing(ArrayList<Integer> scores) {
    // Loop from the end of the list to the beginning
    for (int i = scores.size() - 1; i >= 0; i--) {
        if (scores.get(i) < 60) {
            scores.remove(i); // This is safe now!
        }
    }
}

When you remove an element at index i, the elements you've already checked (at i+1, i+2, etc.) are unaffected. Your next stop is i-1, which is exactly where it should be.

Reversing the Order

To reverse a list, you can swap the first element with the last, the second with the second-to-last, and so on, until you meet in the middle. This is a classic "two-pointer" approach.

public void reverseList(ArrayList<String> items) {
    int left = 0;
    int right = items.size() - 1;

    while (left < right) {
        // Swap the elements at the left and right pointers
        String temp = items.get(left);
        items.set(left, items.get(right));
        items.set(right, temp);

        // Move the pointers closer to the middle
        left++;
        right--;
    }
}

Special Traversals

Accessing Consecutive Pairs

What if you need to compare an element with the one right after it? For example, to find the biggest jump in temperature day-over-day.

The key is to stop your loop one element early to avoid an IndexOutOfBoundsException. Your loop should go up to list.size() - 1.

// Finds the largest increase between consecutive days
for (int i = 0; i < temperatures.size() - 1; i++) {
    int temp1 = temperatures.get(i);
    int temp2 = temperatures.get(i + 1);
    int difference = temp2 - temp1;
    // ... do something with the difference
}

Traversing Multiple Lists

Sometimes you need to work with two lists at once. For example, let's say you have two ArrayLists of student names, classA and classB, and you want to find students who are in both. A simple way is with nested loops: for each student in classA, loop through all of classB to see if you find a match.

ArrayList<String> classA = ...;
ArrayList<String> classB = ...;
ArrayList<String> commonStudents = new ArrayList<String>();

for (String studentA : classA) {
    for (String studentB : classB) {
        if (studentA.equals(studentB)) {
            commonStudents.add(studentA);
        }
    }
}

This covers the fundamental patterns. The AP exam will ask you to apply these patterns to new situations, but the core logic of traversing, checking, and modifying remains the same.

See it in action

ArrayList
Java

    
Op 0 / 0

Worked examples

Let's walk through a few problems to see these algorithms in action.

Example 1

Count Above-Average Students

Problem: You have an ArrayList<Integer> representing the final exam scores for your class. Write a method countAboveAverage that first calculates the class average, and then returns the number of students who scored strictly greater than that average.

Solution Walkthrough:

This is a two-pass algorithm. First, we need to traverse the list to calculate the average. Second, we'll traverse it again to compare each score to that average.

  1. 1
    First Pass: Calculate the Average
    • We'll need a double variable for the sum to prevent integer division.
    • Loop through the scores list and add each score to sum.
    • After the loop, divide sum by scores.size() to get the average. We should also handle the case of an empty list to avoid dividing by zero.
  2. 2
    Second Pass: Count the Students
    • Initialize an int counter, say numAboveAverage, to 0.
    • Loop through the scores list again.
    • Inside the loop, check if score > average.
    • If it is, increment numAboveAverage.
  3. 3
    Return the Count
    After the second loop finishes, numAboveAverage holds our answer.
public int countAboveAverage(ArrayList<Integer> scores) {
    if (scores.size() == 0) {
        return 0;
    }

    // --- Pass 1: Calculate Average ---
    double sum = 0.0;
    for (int score : scores) {
        sum += score;
    }
    double average = sum / scores.size();

    // --- Pass 2: Count Scores Above Average ---
    int numAboveAverage = 0;
    for (int score : scores) {
        if (score > average) {
            numAboveAverage++;
        }
    }

    return numAboveAverage;
}
Example 2

Remove Duplicate Names

Problem: You're cleaning up a contact list, represented by an ArrayList<String>. Write a method removeDuplicates that removes any name that has already appeared earlier in the list. The first occurrence of each name should be kept.

Solution Walkthrough:

This is a great place for nested loops. The outer loop will pick an element, and the inner loop will check the rest of the list for duplicates of it.

  1. 1
    Outer Loop
    We'll iterate through the list with an index i. This pointer marks the element we're checking for duplicates.
  2. 2
    Inner Loop
    For each element at i, we need to scan the rest of the list for matches. This inner loop, using index j, should start from i + 1.
  3. 3
    The Catch
    We're removing elements, so we need to be careful with our indices! If we use a standard forward loop and remove an element at j, the list shrinks, and our j counter will skip the next element. The safest bet is to traverse the inner loop backwards.

Let's try a different, and perhaps simpler, approach. We can build a new list that only contains unique elements.

  1. 1
    Initialize a New List
    Create an empty ArrayList<String> called uniques.
  2. 2
    Traverse the Original List
    Loop through the input list, contacts.
  3. 3
    Check for Existence
    For each name in contacts, check if our uniques list already contains it. The ArrayList contains() method is perfect for this.
  4. 4
    Add if Not Present
    If uniques.contains(name) is false, then we haven't seen this name before. Add it to uniques.
  5. 5
    Return the New List
    After the loop, uniques will hold the desired result.
public ArrayList<String> removeDuplicates(ArrayList<String> contacts) {
    ArrayList<String> uniqueContacts = new ArrayList<String>();

    for (String name : contacts) {
        // If the new list doesn't already have this name, add it.
        if (!uniqueContacts.contains(name)) {
            uniqueContacts.add(name);
        }
    }
    return uniqueContacts;
}

This approach is often cleaner and less error-prone than modifying the list in place, especially for more complex removal logic.

Tracing the first pass of `countAboveAverage` to calculate the sum.
Why a two-pass approach is necessary for calculating above-average counts.

Try it yourself

Ready to try a couple on your own? Don't worry about getting it perfect on the first try. The goal is to think through the logic.

Problem 1: Shift Left

Write a method shiftLeft(ArrayList<String> words) that "rotates" all elements one position to the left. The element that was at index 0 should move to the end of the list.

For example, if the list is ["a", "b", "c", "d"], after the method runs it should be ["b", "c", "d", "a"].

Problem 2: Is it a Palindrome?

Write a boolean method isPalindrome(ArrayList<Integer> nums) that returns true if the list reads the same forwards and backwards, and false otherwise.

For example, [1, 2, 3, 2, 1] is a palindrome. [1, 2, 3, 4, 5] is not.

Visualizing the `shiftLeft` operation on an ArrayList.
Using two pointers to check if an ArrayList is a palindrome.