Data Structures: Arrays, Lists, and Stacks in Java
Implementation and application of fundamental data structures like arrays, ArrayLists, and Stacks in Java for solving competitive programming problems.
ArrayLists in Java for Competitive Programming
What are ArrayLists in Java?
An ArrayList
in Java is a resizable array implementation of the List
interface. Unlike arrays, which have a fixed size determined at the time of creation, ArrayLists can dynamically grow or shrink as elements are added or removed. This makes them incredibly versatile for scenarios where the number of elements is unknown or changes frequently. They are part of the java.util
package, so you'll need to import them: import java.util.ArrayList;
.
Exploring ArrayLists in Java
Dynamic Resizing
The key advantage of an ArrayList is its ability to resize automatically. When you add an element to a full ArrayList, it creates a new, larger array and copies the existing elements over. This process happens behind the scenes, freeing you from manually managing array sizes. While resizing has a time complexity of O(n), it's amortized over many operations, resulting in an average insertion time complexity of O(1) for adding elements at the end.
Example:
import java.util.ArrayList;
public class ArrayListResizeExample {
public static void main(String[] args) {
ArrayList<Integer> numbers = new ArrayList<>();
for (int i = 0; i < 10; i++) {
numbers.add(i);
System.out.println("Size: " + numbers.size() + ", Capacity: " + getCapacity(numbers)); //Capacity is not directly accessible
}
}
//A hacky way to estimate capacity - Don't rely on this in real code.
private static int getCapacity(ArrayList> l) {
try {
java.lang.reflect.Field dataField = ArrayList.class.getDeclaredField("elementData");
dataField.setAccessible(true);
return ((Object[]) dataField.get(l)).length;
} catch (NoSuchFieldException e) {
return -1; // or other sentinel value
} catch (IllegalAccessException e) {
return -2; //or other sentinel value
}
}
}
Insertion
You can insert elements into an ArrayList at specific positions using the add(int index, E element)
method. This shifts existing elements to make room for the new one. Insertion at the beginning of an ArrayList is O(n) due to the shifting of elements. Adding to the end (add(E element)
) is generally O(1) amortized.
Example:
import java.util.ArrayList;
public class ArrayListInsertion {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add(1, "Charlie"); // Inserts "Charlie" at index 1, shifting "Bob" to index 2
System.out.println(names); // Output: [Alice, Charlie, Bob]
}
}
Deletion
Elements can be removed from an ArrayList using the remove(int index)
or remove(Object obj)
methods. Removing an element at a specific index also involves shifting subsequent elements, resulting in O(n) time complexity. Removing by object searches for the first occurrence of the object and removes it, also typically O(n) in the worst case.
Example:
import java.util.ArrayList;
public class ArrayListDeletion {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
names.remove(1); // Removes "Bob" at index 1
System.out.println(names); // Output: [Alice, Charlie]
names.remove("Alice"); // Removes the object "Alice"
System.out.println(names); // Output: [Charlie]
}
}
Searching
ArrayLists provide the contains(Object obj)
method to check if an element exists and the indexOf(Object obj)
method to find the index of the first occurrence of an element. Both methods have a time complexity of O(n) as they may need to iterate through the entire list. For faster searching in sorted ArrayLists, consider using binary search (Collections.binarySearch()
), which has a time complexity of O(log n), but requires the list to be sorted first.
Example:
import java.util.ArrayList;
import java.util.Collections;
public class ArrayListSearching {
public static void main(String[] args) {
ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);
numbers.add(30);
System.out.println(numbers.contains(20)); // Output: true
System.out.println(numbers.indexOf(30)); // Output: 2
Collections.sort(numbers); // Sort for binary search
int index = Collections.binarySearch(numbers, 20);
System.out.println("Binary Search Index: " + index); //Output: Binary Search Index: 1
}
}
Advantages of ArrayLists over Arrays in Competitive Programming
In competitive programming, the dynamic resizing of ArrayLists is a significant advantage over arrays in several situations:
- Unknown Input Size: When the size of the input is not known beforehand, using a fixed-size array becomes problematic. ArrayLists allow you to accommodate the input regardless of its size.
- Frequent Insertions/Deletions: Problems involving frequent insertion or deletion of elements, especially in the middle of the sequence, are much easier to solve with ArrayLists. Arrays would require manual shifting of elements, leading to complex and potentially error-prone code.
- Simplicity and Readability: ArrayLists provide a more concise and readable syntax for common operations like adding, removing, and searching elements. This can save time and reduce the likelihood of bugs during a contest.
However, it's important to remember that ArrayLists have a slight performance overhead compared to arrays due to the dynamic resizing and the object-oriented nature of Java. If the size of the data is known beforehand and performance is absolutely critical, arrays might be a better choice. But for most cases, the convenience and flexibility of ArrayLists outweigh the performance difference.
Competitive Programming in Java with Solutions
Problem 1: Dynamic Array Sum
Problem Statement: You are given a series of integers as input. The number of integers is not known in advance. Store the integers in a dynamic array and calculate their sum.
Solution:
import java.util.ArrayList;
import java.util.Scanner;
public class DynamicArraySum {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
ArrayList<Integer> numbers = new ArrayList<>();
// Read integers until there are no more inputs (e.g., end-of-file or a specific sentinel value).
while (scanner.hasNextInt()) { //Using scanner.hasNextInt to check for more numbers.
numbers.add(scanner.nextInt());
}
int sum = 0;
for (int number : numbers) {
sum += number;
}
System.out.println("Sum: " + sum);
scanner.close();
}
}
Problem 2: Remove Duplicates
Problem Statement: Given a list of strings, remove all duplicate strings while preserving the original order.
Solution:
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.Scanner;
import java.util.Set;
public class RemoveDuplicates {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
ArrayList<String> inputList = new ArrayList<>();
// Read the number of strings to input.
int n = scanner.nextInt();
scanner.nextLine(); // Consume the newline character after reading the integer.
// Read each string and add it to the list.
for (int i = 0; i < n; i++) {
inputList.add(scanner.nextLine());
}
// Use LinkedHashSet to remove duplicates and preserve order.
Set<String> uniqueStrings = new LinkedHashSet<>(inputList);
ArrayList<String> resultList = new ArrayList<>(uniqueStrings); //Convert back to ArrayList
System.out.println(resultList);
scanner.close();
}
}
Problem 3: Finding the Kth Largest Element
Problem Statement: Given an unsorted array of integers, find the kth largest element in it.
Solution:
import java.util.ArrayList;
import java.util.Collections;
import java.util.PriorityQueue;
import java.util.Scanner;
public class KthLargestElement {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
ArrayList<Integer> numbers = new ArrayList<>();
// Input array size and K
int n = scanner.nextInt();
int k = scanner.nextInt();
// Reading in the array elements
for (int i = 0; i < n; i++) {
numbers.add(scanner.nextInt());
}
// Using min-heap of size k for efficiency
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
for (int num : numbers) {
minHeap.add(num);
if (minHeap.size() > k) {
minHeap.poll();
}
}
System.out.println("The " + k + "th largest element is: " + minHeap.peek());
scanner.close();
}
}