Welcome to another week of competitive programming of Spring 2025! Today’s problem solving session will focus on fundamental data structures. You will be using things like stacks, heaps, and binary search trees.
But first, if this is your first time ever in one of our competitive programming sessions, please see our introduction guide to get started. Once you’re done, feel free to come back here.
To make the most out of your experience in these sessions you should read the introduction and then attempt to solve the problems one by one, i.e. don’t try working on B before solving A, C before solving A, etc. You don’t have to solve all the problems, you will be learning something even if you only try to solve a single problem.
Feel free to work together with a friend or two, this is supposed to be a collaborative environment. Also, if you are stuck or have questions, don’t keep them to yourself! Talk to other people! If you are not sure who to talk to, ask on discord.
This week, we focus on efficiently handling queries using built-in
data structures in Java, including Stack
, Queue
, TreeSet
,
TreeMap
, and PriorityQueue
(i.e. a heap). These structures allow us to
efficiently manage and process data while solving competitive
programming problems. Understanding how to leverage these built-in
classes will help us implement efficient solutions.
Here is an overview of these with some Java code samples.
A Stack
is a Last-In-First-Out (LIFO) data structure that allows push and pop operations.
import java.util.Stack;
public class StackExample {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
stack.push(5);
stack.push(10);
System.out.println(stack.pop()); // 10
System.out.println(stack.peek()); // 5
}
}
A Queue
follows a First-In-First-Out (FIFO) approach.
import java.util.LinkedList;
import java.util.Queue;
public class QueueExample {
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();
queue.offer(1);
queue.offer(2);
System.out.println(queue.poll()); // 1
System.out.println(queue.peek()); // 2
}
}
A TreeSet
maintains elements in sorted order and provides logarithmic time complexity for operations.
import java.util.TreeSet;
public class TreeSetExample {
public static void main(String[] args) {
TreeSet<Integer> treeSet = new TreeSet<>();
treeSet.add(5);
treeSet.add(1);
treeSet.add(10);
System.out.println(treeSet.first()); // 1
System.out.println(treeSet.last()); // 10
}
}
A TreeMap
is a sorted key-value store, useful for range queries.
import java.util.TreeMap;
public class TreeMapExample {
public static void main(String[] args) {
TreeMap<Integer, String> treeMap = new TreeMap<>();
treeMap.put(2, "B");
treeMap.put(1, "A");
treeMap.put(3, "C");
System.out.println(treeMap.firstEntry()); // 1=A
System.out.println(treeMap.lastEntry()); // 3=C
}
}
A PriorityQueue
provides a min-heap or max-heap implementation.
import java.util.PriorityQueue;
public class PriorityQueueExample {
public static void main(String[] args) {
PriorityQueue<Integer> pq = new PriorityQueue<>(); // Min-heap
pq.offer(10);
pq.offer(5);
pq.offer(20);
System.out.println(pq.poll()); // 5 (smallest element)
}
}
These data structures form the backbone of efficient algorithms in competitive programming. Mastering their applications will help you solve a variety of query-based problems effectively.
You can find the problem description here. Read it and then come back here.
Given the constraints, you need to solve this in time $\Theta(q \log q)$ or better. Think about how to do this using some of the data structures mentioned above. If you are stuck, the next paragraph gives a big hint.
To efficiently handle login credentials, we need a data structure that
supports fast insertions and lookups. A TreeMap<String, String>
in
Java allows storing netids as keys and passwords as values with a time
complexity of $O(\log n)$ for both operations.
–
You can find the problem description here. Read it and then come back here.
The problem requires us to efficiently find the first taller student in front of a given student. A naive approach of checking every student ahead would be too slow $O(n^2)$. Instead, we want a solution running in $O(n)$ time. Think about how to do this using some of the data structures mentioned above. If you are stuck, the next paragraph gives a big hint.
To solve the problem efficiently, use a Stack<Integer>
to keep track
of indices of students. Iterate through the array from left to right,
maintaining a decreasing stack so that when a taller student appears,
all shorter students behind them can be updated.
Note that this problem has a multitest case structure, meaning that your program has to process and solve multiple test cases.
You can find the problem description here. Read it and then come back here.
Given the constraints, you need to solve this in time $\Theta(n \log n)$ or better. Think about how to do this using some of the data structures mentioned above. If you are stuck, the next paragraph gives a big hint.
This is also supposed to be a straightforward application of a data structure, but of a map/dictionary/symbol table. First insert the input into the data structure using the words as keys and the values as a count of their frequency, and then go through the input again to tally up the points. To solve this problem you should learn how to use the default map/dictionary/symbol table data structure from your favorite programming language and then use it.
–
You can find the problem description here. Read it and then come back here.
Given the constraints, you need to solve this in time $\Theta(n \log n)$ or better. Think about how to do this using some of the data structures mentioned above. If you are stuck, the next paragraph gives a big hint.
To solve the problem efficiently, use three PriorityQueue<Integer>
structures (one for each color). Insert t-shirt prices into the
respective PriorityQueue
based on front and back colors. For each
buyer, retrieve and remove the lowest available price for their
preferred color using poll()
.
You can find the problem description here. Read it and then come back here.
This is the most challenging problem and uses a technique that is not usually very well-known called difference arrays. Make sure you know how it works first (here is an article about it). Then to apply it to this problem you have to do it twice, once to each of the m operations, and then you have to use a “difference array of difference arrays” approach to apply each one of the $k$ queries. It’s a bit tricky, but it’s very worth it once you solve it.