Home/Data structures & algorithms/Stacks and nested operations

Stacks and nested operations

Validate last-in-first-out order with a stack

A trace records resources loaded inside nested scopes and unloaded as those scopes exit. Counts alone can look correct even when the nesting is wrong: LOAD:a, LOAD:b, UNLOAD:a, UNLOAD:b loads and unloads both labels, but closes the outer resource while the inner one is still active.

A stack keeps exactly the information the trace needs: which unfinished operation must close next. Sorting the tokens or counting each label would discard that order.

Checking the order of nested resources

Validate nested LOAD/UNLOAD operations with a left-to-right failure rule. Balanced label counts do not establish that unloads close the most recent outstanding load.

Aggregate counts lose nesting. Adjacent-pair reduction can model cancellation but must not inspect malformed tokens after an earlier ordering violation. A stack directly stores the unresolved obligations in reverse arrival order.

Closing the most recent load

Push a, then b. The only valid next unload is b, leaving a on top. An unload on an empty stack is invalid, and leftover labels at the end mean scopes never closed. Parsing also follows the trace: once an ordering violation decides the result, a later malformed token must not replace that result with a different error.

A stack models obligations in reverse arrival order. Counts cannot prove nesting: LOAD:a LOAD:b UNLOAD:a UNLOAD:b has balanced counts and invalid order. Parse a token only when processing reaches it; an earlier ordering violation must return false before a later malformed token is examined. Final O(n) time, O(depth) space.

ArrayDeque exposes stack operations without allowing null elements. The top is the most recently loaded label:

java
import java.util.*;

class Example {
    public static void main(String[] args) {
        Deque<String> pending = new ArrayDeque<>();
        pending.push("a");
        pending.push("b");
        assert pending.peek().equals("b");
        assert !pending.peek().equals("a");
        assert pending.size() == 2;
    }
}

An UNLOAD:a at this point is an ordering failure even though a is present deeper in the stack. The public method must stop there rather than parse a later token. That precedence is observable behavior, not merely an optimization.

Stopping at the first invalid operation

Stack obligations appear in parsing, bracket matching, scoped resource operations, and undo histories. Choose a stack when the most recent unresolved operation must be handled first.

Tokens must be exactly LOAD:label or UNLOAD:label with a nonempty label containing no colon. Reached malformed tokens raise IllegalArgumentException. Return false immediately on the first LIFO violation; otherwise require an empty stack at end. Empty input returns true.

Numeric arithmetic uses long; indices use int. Inputs follow the declared types and contain no null elements unless the API permits them. Preserve caller-owned collections, including on failure, and use IllegalArgumentException for the documented invalid arguments.

Balanced load/unload totals are only part of the trace contract; the resource lifetimes must also nest correctly.

Practice Stacks and nested operations

Implement the exercise in a full-screen workspace, then run its tests.

Open practice
Next lessonMonotonic deques