Implementing ArrayList Algorithms
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;
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.
// 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()).
// 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
booleanflag set tofalse. - Loop through the list. If you find an element that meets the condition, set the flag to
trueand you canbreakout of the loop immediately. There's no need to keep looking.
- Start with a
- 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
falseandbreak.
- This one is a bit counter-intuitive. Start by assuming the answer is
"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
Worked examples
Let's walk through a few problems to see these algorithms in action.
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.
- 1First Pass: Calculate the Average
- We'll need a
doublevariable for thesumto prevent integer division. - Loop through the
scoreslist and add each score tosum. - After the loop, divide
sumbyscores.size()to get theaverage. We should also handle the case of an empty list to avoid dividing by zero.
- We'll need a
- 2Second Pass: Count the Students
- Initialize an
intcounter, saynumAboveAverage, to 0. - Loop through the
scoreslist again. - Inside the loop, check if
score > average. - If it is, increment
numAboveAverage.
- Initialize an
- 3Return the CountAfter the second loop finishes,
numAboveAverageholds 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;
}
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.
- 1Outer LoopWe'll iterate through the list with an index
i. This pointer marks the element we're checking for duplicates. - 2Inner LoopFor each element at
i, we need to scan the rest of the list for matches. This inner loop, using indexj, should start fromi + 1. - 3The CatchWe'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 ourjcounter 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.
- 1Initialize a New ListCreate an empty
ArrayList<String>calleduniques. - 2Traverse the Original ListLoop through the input list,
contacts. - 3Check for ExistenceFor each
nameincontacts, check if ouruniqueslist already contains it. TheArrayListcontains()method is perfect for this. - 4Add if Not PresentIf
uniques.contains(name)isfalse, then we haven't seen this name before. Add it touniques. - 5Return the New ListAfter the loop,
uniqueswill 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.
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.
Practice — 8 questions
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.
// 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;
}
- 4.10.A: Develop code for standard and original algorithms for a particular context or specification that involve ArrayList objects and determine the result of these algorithms.
- 4.10.A.1
- There are standard ArrayList algorithms that utilize traversals to: • determine a minimum or maximum value • compute a sum or average • determine if at least one element has a particular property • determine if all elements have a particular property • determine the number of elements having a particular property • access all consecutive pairs of elements • determine the presence or absence of duplicate elements • shift or rotate elements left or right • reverse the order of the elements • insert elements • delete elements
- 4.10.A.2
- Some algorithms require multiple String, array, or ArrayList objects to be traversed simultaneously.
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;
Read what Saavi narrates
Hi everyone, it's Saavi. Let's talk about something you already do all the time, maybe without realizing it.
Imagine you're getting ready for a long road trip, maybe from Chicago all the way to Seattle. You're in charge of the playlist. You've got all your songs in a list on your phone. But a great playlist needs some work, right? You might want to find the longest song for that really boring part of the drive. Or maybe you need to remove that one song your friend added as a joke.
These tasks... finding a maximum value, or removing an item from a list... are exactly what we're learning to code today. We're going to learn the standard recipes, or algorithms, for managing an ArrayList. At its core, this is all about using loops to walk through a list and do something useful at each step.
Let's take a tricky but common example. Say you have an ArrayList of student GPAs, and you need to write a method to remove all the failing ones, let's say any GPA below 2.0.
Your first instinct might be to loop from the beginning to the end. But watch out... this is a classic trap. Imagine your list is like a line of people. If you ask the person at position 3 to leave, the person from position 4 immediately steps up to fill the gap. But your code has already moved on to check position 4, completely skipping the person who just moved! You'll miss checking some of the GPAs.
So, here's the pro move: loop backwards. Start at the very end of the list and work your way to the front. If you're at the last person in line and ask them to leave, it doesn't affect anyone else you still need to check. You can safely remove the GPA at the current index, and then move on to the next one... which is the one right before it. This simple trick of looping backwards solves the whole problem.
This brings up one of the most common mistakes I see. Students often try to remove items from a list while looping forwards. It feels logical, but it causes you to skip elements. So, remember this rule: if you need to remove items from an ArrayList while looping through it, always loop from the end to the beginning. It's a simple change that makes your code robust and correct.
You're building a toolkit of problem-solving patterns. Keep practicing them, and they'll start to feel like second nature. You've got this.
A `for-each` loop uses an internal iterator. Adding or removing elements while it's running confuses the iterator and throws a `ConcurrentModificationException`.
Use a standard `for` loop with an index, and if you're removing elements, iterate backwards (from `list.size() - 1` down to `0`).
Your loop condition is `i < list.size()`, but inside you access `list.get(i + 1)`. When `i` is at the last index (`list.size() - 1`), `i + 1` is out of bounds.
Stop the loop one element early. Use the condition `i < list.size() - 1`.
When you call `list.remove(i)`, the element at `i+1` shifts to index `i`. The loop then increments `i`, skipping over the element that just shifted into the current position.
Either iterate backwards (`for (int i = list.size() - 1; i >= 0; i--)`) or, after removing, decrement the loop counter (`i--`) to re-evaluate the new element at the current index. The backwards loop is generally safer and cleaner.
Setting `int max = 0;` fails if all the numbers in the list are negative. The algorithm would incorrectly return 0 as the maximum.
Initialize your `min` or `max` variable to the *first element* in the list (`list.get(0)`). Then, start your loop from the second element (`i = 1`).
In Java, `int / int` results in an `int`. `99 / 100` is `0`, not `0.99`. This can lead to significant precision loss.
Ensure at least one of the operands is a `double`. The easiest way is to declare your `sum` variable as a `double` from the start (`double sum = 0.0;`).