diff --git a/README.md b/README.md
index cf7bf34..ca16a62 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@ One module per article. This repository currently holds:
| [`sliding-window`](sliding-window/) | Sliding Window and Two Pointers in Java: 12 Interview Problems Solved | this commit |
| [`dynamic-programming`](dynamic-programming/) | Dynamic Programming in Java for Beginners: Memoization vs Tabulation (10 Classic Problems) | this commit |
| [`fresher-qa`](fresher-qa/) | Top 50 Java Interview Questions for Freshers (2026) with Simple Answers | this commit |
+| [`stack-queue`](stack-queue/) | Stack and Queue Problems in Java: Balanced Parentheses, Next Greater Element, Min Stack | this commit |
## `collections` module
diff --git a/stack-queue/README.md b/stack-queue/README.md
new file mode 100644
index 0000000..eb418a4
--- /dev/null
+++ b/stack-queue/README.md
@@ -0,0 +1,61 @@
+# stack-queue
+
+Companion code for the ankurm.com article **Stack and Queue Problems in Java: Balanced Parentheses, Next Greater Element, Min Stack**.
+
+Ten classic interview problems on stacks and queues, each cross-checked against an obviously-correct
+brute-force version on seeded random inputs. Every figure and console line in the article is quoted from a
+file in `output/`, which the JUnit tests write while asserting the same numbers. There is no `docs/` folder:
+the explanations live in the article itself.
+
+## Versions
+
+| Component | Version | Verified against |
+|---|---|---|
+| JDK | Temurin 25.0.5+8 (LTS) | `java -version` |
+| JUnit | 6.1.3 | `junit-bom` `maven-metadata.xml` on Maven Central (latest GA) |
+| Maven | 3.9.x | `mvn -version` |
+
+## Quickstart
+
+```bash
+cd stack-queue
+export JAVA_HOME=/path/to/jdk-25 PATH="$JAVA_HOME/bin:$PATH"
+./scripts/run-all.sh # runs 11 tests and regenerates output/*.txt
+```
+
+The first Maven run must be online so the plugins land in the local cache.
+
+## The ten problems
+
+| # | Problem | Idea | Method |
+|---|---|---|---|
+| 1 | Balanced parentheses | stack of expected closers | `Parentheses.isBalanced` |
+| 2 | Evaluate Reverse Polish Notation | stack of operands | `Rpn.evaluate` |
+| 3 | Min stack | second stack, or one stack of differences | `MinStack`, `EncodedMinStack` |
+| 4 | Next greater element | monotonic stack | `MonotonicStack.nextGreater`, `nextGreaterSubset` |
+| 5 | Daily temperatures | monotonic stack of indices | `MonotonicStack.dailyTemperatures` |
+| 6 | Next greater element, circular | monotonic stack, two laps | `MonotonicStack.nextGreaterCircular` |
+| 7 | Largest rectangle in a histogram | monotonic stack with a sentinel | `MonotonicStack.largestRectangle` |
+| 8 | Decode string | stack of parked text and counts | `DecodeString.decode` |
+| 9 | Queue using two stacks | pour only when the output stack is empty | `QueueViaStacks` |
+| 10 | Stack using one queue | rotate after every push | `StackViaQueue` |
+
+## Source layout
+
+```
+stack-queue/
+├── pom.xml
+├── scripts/run-all.sh
+├── src/main/java/com/ankurm/interviewlab/stackqueue/
+│ ├── Parentheses.java, Rpn.java, DecodeString.java problems 1, 2, 8
+│ ├── IntMinStack.java, MinStack.java, EncodedMinStack.java problem 3
+│ ├── MonotonicStack.java problems 4-7
+│ ├── QueueViaStacks.java, StackViaQueue.java problems 9, 10
+│ ├── BruteForce.java slow, obviously-correct references
+│ ├── DryRuns.java instrumented copies that record every step
+│ ├── Complexity.java operation counters (counted, not timed)
+│ ├── Pitfalls.java plausible-looking wrong versions
+│ └── JdkStackDemo.java java.util.Stack versus ArrayDeque
+├── src/test/java/.../ SolutionsTest, DryRunTest, RandomAgreementTest, ComplexityTest, PitfallsTest, JdkStackTest
+└── output/*.txt captured real output
+```
diff --git a/stack-queue/output/01-balanced-parentheses-trace.txt b/stack-queue/output/01-balanced-parentheses-trace.txt
new file mode 100644
index 0000000..0a7e77a
--- /dev/null
+++ b/stack-queue/output/01-balanced-parentheses-trace.txt
@@ -0,0 +1,28 @@
+Balanced brackets: every step of four inputs
+============================================
+
+input: {[()]} firstMismatch = -1
+ read '{' at 0: push '}' waiting for (top first): }
+ read '[' at 1: push ']' waiting for (top first): ] }
+ read '(' at 2: push ')' waiting for (top first): ) ] }
+ read ')' at 3: pop ')', matches waiting for (top first): ] }
+ read ']' at 4: pop ']', matches waiting for (top first): }
+ read '}' at 5: pop '}', matches waiting for (top first): (empty)
+ end of input: waiting for (empty) -> balanced
+
+input: ([)] firstMismatch = 2
+ read '(' at 0: push ')' waiting for (top first): )
+ read '[' at 1: push ']' waiting for (top first): ] )
+ read ')' at 2: expected ']' -> NOT balanced
+
+input: ()) firstMismatch = 2
+ read '(' at 0: push ')' waiting for (top first): )
+ read ')' at 1: pop ')', matches waiting for (top first): (empty)
+ read ')' at 2: stack is empty, nothing to close -> NOT balanced
+
+input: (() firstMismatch = 0
+ read '(' at 0: push ')' waiting for (top first): )
+ read '(' at 1: push ')' waiting for (top first): ) )
+ read ')' at 2: pop ')', matches waiting for (top first): )
+ end of input: waiting for ) -> NOT balanced, openers left over
+
diff --git a/stack-queue/output/02-rpn-trace.txt b/stack-queue/output/02-rpn-trace.txt
new file mode 100644
index 0000000..422c80f
--- /dev/null
+++ b/stack-queue/output/02-rpn-trace.txt
@@ -0,0 +1,17 @@
+Reverse Polish Notation: stack after every token
+================================================
+
+tokens: [2, 1, +, 3, *] result = 9
+ token 2: push 2 stack(top first) = [2]
+ token 1: push 1 stack(top first) = [1, 2]
+ token +: pop 1 then 2, compute 2 + 1 = 3 stack(top first) = [3]
+ token 3: push 3 stack(top first) = [3, 3]
+ token *: pop 3 then 3, compute 3 * 3 = 9 stack(top first) = [9]
+
+tokens: [4, 13, 5, /, +] result = 6
+ token 4: push 4 stack(top first) = [4]
+ token 13: push 13 stack(top first) = [13, 4]
+ token 5: push 5 stack(top first) = [5, 13, 4]
+ token /: pop 5 then 13, compute 13 / 5 = 2 stack(top first) = [2, 4]
+ token +: pop 2 then 4, compute 4 + 2 = 6 stack(top first) = [6]
+
diff --git a/stack-queue/output/03-min-stack-trace.txt b/stack-queue/output/03-min-stack-trace.txt
new file mode 100644
index 0000000..3dfc169
--- /dev/null
+++ b/stack-queue/output/03-min-stack-trace.txt
@@ -0,0 +1,16 @@
+Min stack: both internal stacks after every operation
+=====================================================
+
+push 5 values=[5] mins=[5] (new or equal minimum, also pushed on mins)
+push 3 values=[3, 5] mins=[3, 5] (new or equal minimum, also pushed on mins)
+push 7 values=[7, 3, 5] mins=[3, 5]
+push 3 values=[3, 7, 3, 5] mins=[3, 3, 5] (new or equal minimum, also pushed on mins)
+getMin values=[3, 7, 3, 5] mins=[3, 3, 5] getMin() = 3
+pop -> 3 values=[7, 3, 5] mins=[3, 5] (it was the minimum, mins popped too)
+getMin values=[7, 3, 5] mins=[3, 5] getMin() = 3
+pop -> 7 values=[3, 5] mins=[3, 5]
+getMin values=[3, 5] mins=[3, 5] getMin() = 3
+pop -> 3 values=[5] mins=[5] (it was the minimum, mins popped too)
+getMin values=[5] mins=[5] getMin() = 5
+
+getMin results: [3, 3, 3, 5] (identical for MinStack and EncodedMinStack)
diff --git a/stack-queue/output/04-monotonic-stack-trace.txt b/stack-queue/output/04-monotonic-stack-trace.txt
new file mode 100644
index 0000000..57791d2
--- /dev/null
+++ b/stack-queue/output/04-monotonic-stack-trace.txt
@@ -0,0 +1,17 @@
+Next greater element: the stack of pending indices, step by step
+================================================================
+
+input [2, 1, 2, 4, 3]
+ i=0 value=2: push 0 pending(top first) = [0]
+ i=1 value=1: push 1 pending(top first) = [1, 0]
+ i=2 value=2: pop index 1 (value 1) -> its answer is 2; push 2 pending(top first) = [2, 0]
+ i=3 value=4: pop index 2 (value 2) -> its answer is 4; pop index 0 (value 2) -> its answer is 4; push 3 pending(top first) = [3]
+ i=4 value=3: push 4 pending(top first) = [4, 3]
+ indices still pending have no greater element to their right: [4, 3] -> stay -1
+result [4, 2, 4, -1, -1]
+
+dailyTemperatures [73, 74, 75, 71, 69, 72, 76, 73]
+ = [1, 1, 4, 2, 1, 1, 0, 0]
+
+nextGreaterCircular [1, 2, 1] = [2, -1, 2]
+nextGreaterSubset [4, 1, 2] within [1, 3, 4, 2] = [-1, 3, -1]
diff --git a/stack-queue/output/05-histogram-trace.txt b/stack-queue/output/05-histogram-trace.txt
new file mode 100644
index 0000000..5d08a7c
--- /dev/null
+++ b/stack-queue/output/05-histogram-trace.txt
@@ -0,0 +1,25 @@
+Largest rectangle in a histogram: pops, widths and areas
+========================================================
+
+heights [2, 1, 5, 6, 2, 3]
+ i=0 height=2:
+ push index 0 stack(top first) = [0]
+ i=1 height=1:
+ pop bar of height 2: left edge index -1, right edge index 0, width 1, area 2 best = 2
+ push index 1 stack(top first) = [1]
+ i=2 height=5:
+ push index 2 stack(top first) = [2, 1]
+ i=3 height=6:
+ push index 3 stack(top first) = [3, 2, 1]
+ i=4 height=2:
+ pop bar of height 6: left edge index 2, right edge index 3, width 1, area 6 best = 6
+ pop bar of height 5: left edge index 1, right edge index 3, width 2, area 10 best = 10
+ push index 4 stack(top first) = [4, 1]
+ i=5 height=3:
+ push index 5 stack(top first) = [5, 4, 1]
+ i=6 (sentinel) height=0:
+ pop bar of height 3: left edge index 4, right edge index 5, width 1, area 3 best = 10
+ pop bar of height 2: left edge index 1, right edge index 5, width 4, area 8 best = 10
+ pop bar of height 1: left edge index -1, right edge index 5, width 6, area 6 best = 10
+ push index 6 stack(top first) = [6]
+largest rectangle = 10
diff --git a/stack-queue/output/06-queue-stack-conversions-trace.txt b/stack-queue/output/06-queue-stack-conversions-trace.txt
new file mode 100644
index 0000000..7a793a4
--- /dev/null
+++ b/stack-queue/output/06-queue-stack-conversions-trace.txt
@@ -0,0 +1,16 @@
+Queue from two stacks, and stack from one queue
+===============================================
+
+QueueViaStacks (stacks shown top first)
+ enqueue(1) in=[1] out=[]
+ enqueue(2) in=[2, 1] out=[]
+ enqueue(3) in=[3, 2, 1] out=[]
+ dequeue() = 1 in=[] out=[2, 3] transfers so far: 3
+ enqueue(4) in=[4] out=[2, 3]
+ dequeue() x3 = 2, 3, 4 in=[] out=[] transfers so far: 4
+
+StackViaQueue
+ push(1) queue(front=top)=[1] rotations so far: 0
+ push(2) queue(front=top)=[2, 1] rotations so far: 1
+ push(3) queue(front=top)=[3, 2, 1] rotations so far: 3
+ pop() x2 = 3, 2 queue(front=top)=[1]
diff --git a/stack-queue/output/07-all-ten-results.txt b/stack-queue/output/07-all-ten-results.txt
new file mode 100644
index 0000000..86b5c39
--- /dev/null
+++ b/stack-queue/output/07-all-ten-results.txt
@@ -0,0 +1,13 @@
+All ten problems on their headline examples
+===========================================
+
+ 1. isBalanced("{[()]}") = true; isBalanced("([)]") = false
+ 2. evaluate([2, 1, +, 3, *]) = 9; 7 / -3 truncates toward zero: -2
+ 3. push -2, 0, -3: getMin = -3; pop; top = 0, getMin = -2 (both implementations agree)
+ 4. nextGreater([1, 3, 4, 2]) = [3, 4, -1, -1]
+ 5. dailyTemperatures([73, 74, 75, 71, 69, 72, 76, 73]) = [1, 1, 4, 2, 1, 1, 0, 0]
+ 6. nextGreaterCircular([1, 2, 3, 4, 3]) = [2, 3, 4, -1, 4]
+ 7. largestRectangle([2, 1, 5, 6, 2, 3]) = 10
+ 8. decode("3[a2[bc]]") = "abcbcabcbcabcbc"; decode("2[abc]3[cd]ef") = "abcabccdcdcdef"
+ 9. enqueue a, b, c; dequeue = a; enqueue d; dequeue = b; peek = c; size = 2
+10. push a, b, c; top = c; pop = c; pop = b; size = 1
diff --git a/stack-queue/output/08-random-agreement.txt b/stack-queue/output/08-random-agreement.txt
new file mode 100644
index 0000000..279da34
--- /dev/null
+++ b/stack-queue/output/08-random-agreement.txt
@@ -0,0 +1,12 @@
+Random agreement with brute force (seed 20261112, 5000 cases per row)
+=====================================================================
+
+nextGreater vs scan-right 5000 cases, 0 mismatches
+dailyTemperatures vs scan-right 5000 cases, 0 mismatches
+nextGreaterCircular vs wrap-around scan 5000 cases, 0 mismatches
+largestRectangle vs widen-each-bar 5000 cases, 0 mismatches
+isBalanced vs delete-adjacent-pairs 5000 cases, 0 mismatches
+decode vs recursive descent 5000 cases, 0 mismatches
+evaluate (RPN) vs expression-tree evaluation 5000 cases, 0 mismatches
+MinStack and EncodedMinStack vs recompute-the-min 5000 sequences (150000 operations), 0 mismatches
+QueueViaStacks and StackViaQueue vs ArrayDeque 5000 sequences (150000 operations), 0 mismatches
diff --git a/stack-queue/output/09-operation-counts.txt b/stack-queue/output/09-operation-counts.txt
new file mode 100644
index 0000000..e0fced1
--- /dev/null
+++ b/stack-queue/output/09-operation-counts.txt
@@ -0,0 +1,24 @@
+Operation counts (counted, not timed)
+=====================================
+
+nextGreater on a strictly decreasing array (the worst case for the scan-right version)
+ n scan-right compares stack compares stack push+pop
+ 1000 499500 999 1000
+ 10000 49995000 9999 10000
+ 100000 4999950000 99999 100000
+
+nextGreater on random values (n = 100000): every index is pushed once and popped at most once
+pushes = 100000, pops = 99985, comparisons = 199977 (bound: comparisons <= 2n = 200000)
+
+QueueViaStacks: n enqueues followed by n dequeues
+ n total element moves moves in the 1st dequeue
+ 1000 1000 1000
+ 10000 10000 10000
+ 100000 100000 100000
+one dequeue can cost n moves, but each element moves exactly once, so n enqueues + n dequeues cost 3n steps in all
+
+StackViaQueue: n pushes
+ n total rotations n(n-1)/2
+ 100 4950 4950
+ 1000 499500 499500
+ 5000 12497500 12497500
diff --git a/stack-queue/output/10-stack-vs-arraydeque.txt b/stack-queue/output/10-stack-vs-arraydeque.txt
new file mode 100644
index 0000000..b3ea399
--- /dev/null
+++ b/stack-queue/output/10-stack-vs-arraydeque.txt
@@ -0,0 +1,19 @@
+java.util.Stack versus ArrayDeque used as a stack
+=================================================
+
+ArrayDeque as a stack: push(1), push(2), push(3), then pop until empty -> [3, 2, 1]
+ArrayDeque as a queue: offer(1), offer(2), offer(3), then poll until empty -> [1, 2, 3]
+
+push(1), push(2), push(3), then iterate:
+ java.util.Stack -> [1, 2, 3] (bottom to top)
+ ArrayDeque -> [3, 2, 1] (top to bottom)
+
+Stack.class.getSuperclass() = java.util.Vector
+Stack.pop() is synchronized = true
+ArrayDeque.pop() is synchronized = false
+
+push(null) on Stack = accepted
+push(null) on ArrayDeque = java.lang.NullPointerException
+
+ArrayDeque: push(1), push(2), peek() = 2 (push is addFirst)
+ArrayDeque: offer(1), offer(2), peek() = 1 (offer is addLast)
diff --git a/stack-queue/output/11-pitfalls.txt b/stack-queue/output/11-pitfalls.txt
new file mode 100644
index 0000000..dd0c89d
--- /dev/null
+++ b/stack-queue/output/11-pitfalls.txt
@@ -0,0 +1,33 @@
+Eight classic mistakes, each reproduced
+=======================================
+
+1. balanced brackets by counting each kind, with no stack
+ input "([)]": counts-only = true stack = false
+ every count ends at zero, but the ] arrives while the ( is not the innermost open bracket
+
+2. min stack that compares boxed Integers with ==
+ push [10, 20, 5], pop, getMin(): broken = 10 correct = 10
+ push [1000, 2000, 500], pop, getMin(): broken = 500 correct = 1000
+ Integer.valueOf caches -128..127, so small values share one object and == happens to work
+
+3. min stack that pushes onto the min stack only on a strictly smaller value
+ push 3, push 3, pop, getMin(): strict -> NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Deque.peek()" is null
+ correct -> 3 (the stack still holds one 3)
+
+4. encoded min stack with int differences instead of long
+ push Integer.MIN_VALUE, push Integer.MAX_VALUE, top(): int diffs = -2147483648 long diffs = 2147483647
+ MAX_VALUE - MIN_VALUE does not fit in an int, wraps to -1, and -1 looks like "new minimum"
+
+5. next greater element popping on <= instead of <
+ input [2, 2, 3]: buggy = [2, 3, -1] correct = [3, 3, -1]
+ the first 2 is told its next greater element is 2
+
+6. largest rectangle without the final height-0 bar
+ input [2, 4]: buggy = 0 correct = 4
+ nothing ever pops the bars that are still on the stack when the loop ends
+
+7. queue from two stacks that pours on every dequeue
+ enqueue 1, 2; dequeue; enqueue 3; dequeue: first = 1 (ok), second: broken = 3 correct = 2
+ the 3 was poured on top of the 2 that was still waiting in the output stack
+
+8. popping an empty structure: both conversions throw NoSuchElementException, as ArrayDeque.pop does
diff --git a/stack-queue/pom.xml b/stack-queue/pom.xml
new file mode 100644
index 0000000..07a8f10
--- /dev/null
+++ b/stack-queue/pom.xml
@@ -0,0 +1,64 @@
+
+
+ 4.0.0
+ com.ankurm.interviewlab
+ stack-queue
+ 1.0.0
+ jar
+
+ java-interview-lab :: stack-queue
+
+ Runnable companion code for ankurm.com's "Stack and Queue Problems in Java" article: ten interview problems, monotonic stack,
+ min stack, queue from stacks and stack from a queue, brute-force cross-checks, dry-run traces, operation counts and pitfalls.
+
+
+
+ 25
+ UTF-8
+ 6.1.3
+
+
+
+
+
+ org.junit
+ junit-bom
+ ${junit.version}
+ pom
+ import
+
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.junit.platform
+ junit-platform-launcher
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.14.0
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.6.0
+
+ -Xmx512m -Duser.timezone=UTC -Dfile.encoding=UTF-8
+
+
+
+
+
diff --git a/stack-queue/scripts/run-all.sh b/stack-queue/scripts/run-all.sh
new file mode 100755
index 0000000..38df9a6
--- /dev/null
+++ b/stack-queue/scripts/run-all.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+# Regenerates every file in output/. Needs JDK 25 and Maven. The first Maven run must be online.
+# Every file is written by a JUnit test that asserts the same numbers it prints, so the figures
+# in the article are assertions: they fail the build the moment they stop being true.
+set -euo pipefail
+cd "$(dirname "$0")/.."
+mkdir -p output
+mvn -B -q -T1 test
+echo "done:"; ls -1 output
diff --git a/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/BruteForce.java b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/BruteForce.java
new file mode 100644
index 0000000..9ad95ef
--- /dev/null
+++ b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/BruteForce.java
@@ -0,0 +1,106 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import java.util.Arrays;
+
+/** Slow, obviously-correct references. The randomized tests compare every stack solution against these. */
+public final class BruteForce {
+ private BruteForce() {}
+
+ public static int[] nextGreater(int[] a) {
+ int[] result = new int[a.length];
+ for (int i = 0; i < a.length; i++) {
+ result[i] = -1;
+ for (int j = i + 1; j < a.length; j++) {
+ if (a[j] > a[i]) {
+ result[i] = a[j];
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ public static int[] dailyTemperatures(int[] temps) {
+ int[] wait = new int[temps.length];
+ for (int i = 0; i < temps.length; i++) {
+ for (int j = i + 1; j < temps.length; j++) {
+ if (temps[j] > temps[i]) {
+ wait[i] = j - i;
+ break;
+ }
+ }
+ }
+ return wait;
+ }
+
+ public static int[] nextGreaterCircular(int[] a) {
+ int n = a.length;
+ int[] result = new int[n];
+ Arrays.fill(result, -1);
+ for (int i = 0; i < n; i++) {
+ for (int step = 1; step < n; step++) {
+ int candidate = a[(i + step) % n];
+ if (candidate > a[i]) {
+ result[i] = candidate;
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ /** For every bar, widen left and right while the neighbours are at least as tall. */
+ public static int largestRectangle(int[] h) {
+ int best = 0;
+ for (int i = 0; i < h.length; i++) {
+ int left = i;
+ while (left > 0 && h[left - 1] >= h[i]) {
+ left--;
+ }
+ int right = i;
+ while (right < h.length - 1 && h[right + 1] >= h[i]) {
+ right++;
+ }
+ best = Math.max(best, h[i] * (right - left + 1));
+ }
+ return best;
+ }
+
+ /** Keep only brackets, then delete adjacent matching pairs until nothing changes. Balanced iff nothing is left. */
+ public static boolean isBalanced(String s) {
+ String t = s.replaceAll("[^()\\[\\]{}]", "");
+ String before;
+ do {
+ before = t;
+ t = t.replace("()", "").replace("[]", "").replace("{}", "");
+ } while (!t.equals(before));
+ return t.isEmpty();
+ }
+
+ /** Recursive-descent decode: the index travels in a one-element array. */
+ public static String decode(String s) {
+ return decodeFrom(s, new int[] {0});
+ }
+
+ private static String decodeFrom(String s, int[] pos) {
+ StringBuilder out = new StringBuilder();
+ while (pos[0] < s.length() && s.charAt(pos[0]) != ']') {
+ char ch = s.charAt(pos[0]);
+ if (Character.isDigit(ch)) {
+ int count = 0;
+ while (Character.isDigit(s.charAt(pos[0]))) {
+ count = count * 10 + (s.charAt(pos[0]) - '0');
+ pos[0]++;
+ }
+ pos[0]++; // skip the opening bracket
+ String inner = decodeFrom(s, pos);
+ pos[0]++; // skip the closing bracket
+ out.append(inner.repeat(count));
+ } else {
+ out.append(ch);
+ pos[0]++;
+ }
+ }
+ return out.toString();
+ }
+}
diff --git a/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/Complexity.java b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/Complexity.java
new file mode 100644
index 0000000..532f5da
--- /dev/null
+++ b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/Complexity.java
@@ -0,0 +1,52 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Deque;
+
+/** Operation counters. These count work done, they do not time it. */
+public final class Complexity {
+ private Complexity() {}
+
+ public record Counts(long comparisons, long pushes, long pops) {
+ public long stackOps() {
+ return pushes + pops;
+ }
+ }
+
+ /** nextGreater with every comparison, push and pop counted. Returns the answers too, so tests can check them. */
+ public static Counts monotonicNextGreater(int[] a, int[] answerOut) {
+ long comparisons = 0;
+ long pushes = 0;
+ long pops = 0;
+ Arrays.fill(answerOut, -1);
+ Deque pending = new ArrayDeque<>();
+ for (int i = 0; i < a.length; i++) {
+ while (!pending.isEmpty()) {
+ comparisons++;
+ if (a[pending.peek()] >= a[i]) {
+ break;
+ }
+ answerOut[pending.pop()] = a[i];
+ pops++;
+ }
+ pending.push(i);
+ pushes++;
+ }
+ return new Counts(comparisons, pushes, pops);
+ }
+
+ /** The brute-force version, counting each a[j] > a[i] comparison. */
+ public static long bruteForceComparisons(int[] a) {
+ long comparisons = 0;
+ for (int i = 0; i < a.length; i++) {
+ for (int j = i + 1; j < a.length; j++) {
+ comparisons++;
+ if (a[j] > a[i]) {
+ break;
+ }
+ }
+ }
+ return comparisons;
+ }
+}
diff --git a/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/DecodeString.java b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/DecodeString.java
new file mode 100644
index 0000000..3db93fd
--- /dev/null
+++ b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/DecodeString.java
@@ -0,0 +1,34 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+/** Problem 8: decode strings such as 3[a2[bc]]. Each open bracket parks the text built so far, and its count. */
+public final class DecodeString {
+ private DecodeString() {}
+
+ public static String decode(String s) {
+ Deque parked = new ArrayDeque<>();
+ Deque repeats = new ArrayDeque<>();
+ StringBuilder current = new StringBuilder();
+ int number = 0;
+ for (int i = 0; i < s.length(); i++) {
+ char ch = s.charAt(i);
+ if (Character.isDigit(ch)) {
+ number = number * 10 + (ch - '0');
+ } else if (ch == '[') {
+ repeats.push(number);
+ parked.push(current);
+ current = new StringBuilder();
+ number = 0;
+ } else if (ch == ']') {
+ StringBuilder outer = parked.pop();
+ outer.append(current.toString().repeat(repeats.pop()));
+ current = outer;
+ } else {
+ current.append(ch);
+ }
+ }
+ return current.toString();
+ }
+}
diff --git a/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/DryRuns.java b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/DryRuns.java
new file mode 100644
index 0000000..25c94e9
--- /dev/null
+++ b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/DryRuns.java
@@ -0,0 +1,151 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.List;
+
+/**
+ * Instrumented copies of the solutions that record every step. The tests assert each copy returns the same answer
+ * as the real method, so the traces describe what the real code does.
+ */
+public final class DryRuns {
+ private DryRuns() {}
+
+ public record Trace(List lines, T result) {}
+
+ /** Closers waiting on the stack, top first, separated by spaces, so they do not clash with list brackets. */
+ private static String closers(Deque stack) {
+ if (stack.isEmpty()) {
+ return "(empty)";
+ }
+ StringBuilder sb = new StringBuilder();
+ for (char ch : stack) {
+ sb.append(sb.length() == 0 ? "" : " ").append(ch);
+ }
+ return sb.toString();
+ }
+
+ public static Trace balanced(String s) {
+ List lines = new ArrayList<>();
+ Deque expected = new ArrayDeque<>();
+ for (int i = 0; i < s.length(); i++) {
+ char ch = s.charAt(i);
+ String prefix = "read '" + ch + "' at " + i + ": ";
+ if (ch == '(' || ch == '{' || ch == '[') {
+ char closer = ch == '(' ? ')' : ch == '{' ? '}' : ']';
+ expected.push(closer);
+ lines.add(prefix + String.format("%-18s", "push '" + closer + "'") + " waiting for (top first): " + closers(expected));
+ } else {
+ if (expected.isEmpty()) {
+ lines.add(prefix + "stack is empty, nothing to close -> NOT balanced");
+ return new Trace<>(lines, false);
+ }
+ char want = expected.pop();
+ if (want != ch) {
+ lines.add(prefix + "expected '" + want + "' -> NOT balanced");
+ return new Trace<>(lines, false);
+ }
+ lines.add(prefix + String.format("%-18s", "pop '" + want + "', matches") + " waiting for (top first): " + closers(expected));
+ }
+ }
+ boolean ok = expected.isEmpty();
+ lines.add("end of input: waiting for " + closers(expected) + (ok ? " -> balanced" : " -> NOT balanced, openers left over"));
+ return new Trace<>(lines, ok);
+ }
+
+ public static Trace rpn(String[] tokens) {
+ List lines = new ArrayList<>();
+ Deque operands = new ArrayDeque<>();
+ for (String token : tokens) {
+ if (token.length() == 1 && "+-*/".contains(token)) {
+ int right = operands.pop();
+ int left = operands.pop();
+ int value = Rpn.apply(token, left, right);
+ operands.push(value);
+ lines.add("token " + token + ": pop " + right + " then " + left + ", compute " + left + " " + token + " "
+ + right + " = " + value + " stack(top first) = " + operands);
+ } else {
+ operands.push(Integer.parseInt(token));
+ lines.add("token " + token + ": push " + token + " stack(top first) = " + operands);
+ }
+ }
+ return new Trace<>(lines, operands.peek());
+ }
+
+ /** Runs a fixed sequence on the parallel-stack min stack, showing both internal stacks after every step. */
+ public static Trace> minStack(String... ops) {
+ List lines = new ArrayList<>();
+ List minsSeen = new ArrayList<>();
+ Deque values = new ArrayDeque<>();
+ Deque minimums = new ArrayDeque<>();
+ for (String op : ops) {
+ if (op.startsWith("push ")) {
+ int x = Integer.parseInt(op.substring(5));
+ values.push(x);
+ String note = "";
+ if (minimums.isEmpty() || x <= minimums.peek()) {
+ minimums.push(x);
+ note = " (new or equal minimum, also pushed on mins)";
+ }
+ lines.add(String.format("%-9s values=%-14s mins=%-10s%s", op, values, minimums, note).stripTrailing());
+ } else if (op.equals("pop")) {
+ int x = values.pop();
+ String note = "";
+ if (x == minimums.peek()) {
+ minimums.pop();
+ note = " (it was the minimum, mins popped too)";
+ }
+ lines.add(String.format("%-9s values=%-14s mins=%-10s%s", op + " -> " + x, values, minimums, note).stripTrailing());
+ } else {
+ int min = minimums.peek();
+ minsSeen.add(min);
+ lines.add(String.format("%-9s values=%-14s mins=%-10s getMin() = %d", op, values, minimums, min));
+ }
+ }
+ return new Trace<>(lines, minsSeen);
+ }
+
+ public static Trace nextGreater(int[] a) {
+ List lines = new ArrayList<>();
+ int[] result = new int[a.length];
+ Arrays.fill(result, -1);
+ Deque pending = new ArrayDeque<>();
+ for (int i = 0; i < a.length; i++) {
+ StringBuilder step = new StringBuilder("i=" + i + " value=" + a[i] + ":");
+ while (!pending.isEmpty() && a[pending.peek()] < a[i]) {
+ int j = pending.pop();
+ result[j] = a[i];
+ step.append(" pop index ").append(j).append(" (value ").append(a[j]).append(") -> its answer is ").append(a[i]).append(';');
+ }
+ pending.push(i);
+ step.append(" push ").append(i).append(" pending(top first) = ").append(pending);
+ lines.add(step.toString());
+ }
+ lines.add("indices still pending have no greater element to their right: " + pending + " -> stay -1");
+ return new Trace<>(lines, result);
+ }
+
+ public static Trace histogram(int[] heights) {
+ List lines = new ArrayList<>();
+ int n = heights.length;
+ int best = 0;
+ Deque rising = new ArrayDeque<>();
+ for (int i = 0; i <= n; i++) {
+ int current = (i == n) ? 0 : heights[i];
+ lines.add("i=" + i + (i == n ? " (sentinel)" : "") + " height=" + current + ":");
+ while (!rising.isEmpty() && heights[rising.peek()] >= current) {
+ int height = heights[rising.pop()];
+ int leftEdge = rising.isEmpty() ? -1 : rising.peek();
+ int width = i - leftEdge - 1;
+ best = Math.max(best, height * width);
+ lines.add(" pop bar of height " + height + ": left edge index " + leftEdge + ", right edge index " + (i - 1)
+ + ", width " + width + ", area " + (height * width) + " best = " + best);
+ }
+ rising.push(i);
+ lines.add(" push index " + i + " stack(top first) = " + rising);
+ }
+ return new Trace<>(lines, best);
+ }
+}
diff --git a/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/EncodedMinStack.java b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/EncodedMinStack.java
new file mode 100644
index 0000000..bc9b819
--- /dev/null
+++ b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/EncodedMinStack.java
@@ -0,0 +1,64 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.NoSuchElementException;
+
+/**
+ * Problem 3, version B: one stack, one extra variable. Each entry stores its distance from the minimum that was in
+ * force when it was pushed. A negative entry marks a push that created a new minimum, and it carries the old
+ * minimum inside it. The differences are longs because the difference of two ints can overflow an int.
+ */
+public final class EncodedMinStack implements IntMinStack {
+ private final Deque diffs = new ArrayDeque<>();
+ private long min;
+
+ @Override
+ public void push(int x) {
+ if (diffs.isEmpty()) {
+ diffs.push(0L);
+ min = x;
+ return;
+ }
+ diffs.push((long) x - min);
+ if (x < min) {
+ min = x;
+ }
+ }
+
+ @Override
+ public int pop() {
+ requireNonEmpty();
+ long diff = diffs.pop();
+ if (diff < 0) {
+ int removed = (int) min; // a negative entry means the popped value was the minimum
+ min = min - diff; // restore the previous minimum
+ return removed;
+ }
+ return (int) (min + diff);
+ }
+
+ @Override
+ public int top() {
+ requireNonEmpty();
+ long diff = diffs.peek();
+ return diff < 0 ? (int) min : (int) (min + diff);
+ }
+
+ @Override
+ public int getMin() {
+ requireNonEmpty();
+ return (int) min;
+ }
+
+ @Override
+ public int size() {
+ return diffs.size();
+ }
+
+ private void requireNonEmpty() {
+ if (diffs.isEmpty()) {
+ throw new NoSuchElementException("stack is empty");
+ }
+ }
+}
diff --git a/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/IntMinStack.java b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/IntMinStack.java
new file mode 100644
index 0000000..74649fc
--- /dev/null
+++ b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/IntMinStack.java
@@ -0,0 +1,17 @@
+package com.ankurm.interviewlab.stackqueue;
+
+/** A stack of ints that also reports its minimum in O(1). */
+public interface IntMinStack {
+ void push(int x);
+
+ /** Removes and returns the top. Throws NoSuchElementException when empty. */
+ int pop();
+
+ /** Returns the top without removing it. Throws NoSuchElementException when empty. */
+ int top();
+
+ /** Returns the smallest value currently in the stack. Throws NoSuchElementException when empty. */
+ int getMin();
+
+ int size();
+}
diff --git a/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/JdkStackDemo.java b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/JdkStackDemo.java
new file mode 100644
index 0000000..92f00c0
--- /dev/null
+++ b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/JdkStackDemo.java
@@ -0,0 +1,104 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import java.lang.reflect.Modifier;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.Queue;
+import java.util.Stack;
+
+/** What the JDK gives you for a stack, and why the answer is ArrayDeque rather than java.util.Stack. */
+public final class JdkStackDemo {
+ private JdkStackDemo() {}
+
+ /** Last in, first out: push 1, 2, 3, then pop until empty. */
+ public static List stackPopOrder() {
+ Deque stack = new ArrayDeque<>();
+ stack.push(1);
+ stack.push(2);
+ stack.push(3);
+ List popped = new ArrayList<>();
+ while (!stack.isEmpty()) {
+ popped.add(stack.pop());
+ }
+ return popped;
+ }
+
+ /** First in, first out: offer 1, 2, 3, then poll until empty. */
+ public static List queuePollOrder() {
+ Queue queue = new ArrayDeque<>();
+ queue.offer(1);
+ queue.offer(2);
+ queue.offer(3);
+ List polled = new ArrayList<>();
+ while (!queue.isEmpty()) {
+ polled.add(queue.poll());
+ }
+ return polled;
+ }
+
+ /** Items in iteration order after push(1), push(2), push(3) on the legacy class. Bottom to top. */
+ public static List legacyStackOrder() {
+ Stack stack = new Stack<>();
+ stack.push(1);
+ stack.push(2);
+ stack.push(3);
+ return new ArrayList<>(stack);
+ }
+
+ /** Items in iteration order after push(1), push(2), push(3) on an ArrayDeque. Top to bottom. */
+ public static List dequeOrder() {
+ Deque stack = new ArrayDeque<>();
+ stack.push(1);
+ stack.push(2);
+ stack.push(3);
+ return new ArrayList<>(stack);
+ }
+
+ public static String legacySuperclass() {
+ return Stack.class.getSuperclass().getName();
+ }
+
+ public static boolean legacyPopIsSynchronized() throws NoSuchMethodException {
+ return Modifier.isSynchronized(Stack.class.getMethod("pop").getModifiers());
+ }
+
+ public static boolean dequePopIsSynchronized() throws NoSuchMethodException {
+ return Modifier.isSynchronized(ArrayDeque.class.getMethod("pop").getModifiers());
+ }
+
+ /** The legacy class accepts null; ArrayDeque rejects it. Returns the exception class name, or "accepted". */
+ public static String pushNullOnLegacy() {
+ try {
+ new Stack().push(null);
+ return "accepted";
+ } catch (RuntimeException e) {
+ return e.getClass().getName();
+ }
+ }
+
+ public static String pushNullOnDeque() {
+ try {
+ new ArrayDeque().push(null);
+ return "accepted";
+ } catch (RuntimeException e) {
+ return e.getClass().getName();
+ }
+ }
+
+ /** push is addFirst, offer is addLast. Mixing them on one deque reads from the wrong end. */
+ public static int peekAfterPushes() {
+ Deque d = new ArrayDeque<>();
+ d.push(1);
+ d.push(2);
+ return d.peek();
+ }
+
+ public static int peekAfterOffers() {
+ Deque d = new ArrayDeque<>();
+ d.offer(1);
+ d.offer(2);
+ return d.peek();
+ }
+}
diff --git a/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/MinStack.java b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/MinStack.java
new file mode 100644
index 0000000..d41846c
--- /dev/null
+++ b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/MinStack.java
@@ -0,0 +1,52 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.NoSuchElementException;
+
+/** Problem 3, version A: a second stack that remembers the minimum at every depth where it changed. */
+public final class MinStack implements IntMinStack {
+ private final Deque values = new ArrayDeque<>();
+ private final Deque minimums = new ArrayDeque<>();
+
+ @Override
+ public void push(int x) {
+ values.push(x);
+ if (minimums.isEmpty() || x <= minimums.peek()) { // <=, not <: a duplicate minimum needs its own entry
+ minimums.push(x);
+ }
+ }
+
+ @Override
+ public int pop() {
+ requireNonEmpty();
+ int x = values.pop();
+ if (x == minimums.peek()) { // int against Integer: unboxed, compared by value
+ minimums.pop();
+ }
+ return x;
+ }
+
+ @Override
+ public int top() {
+ requireNonEmpty();
+ return values.peek();
+ }
+
+ @Override
+ public int getMin() {
+ requireNonEmpty();
+ return minimums.peek();
+ }
+
+ @Override
+ public int size() {
+ return values.size();
+ }
+
+ private void requireNonEmpty() {
+ if (values.isEmpty()) {
+ throw new NoSuchElementException("stack is empty");
+ }
+ }
+}
diff --git a/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/MonotonicStack.java b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/MonotonicStack.java
new file mode 100644
index 0000000..8043d59
--- /dev/null
+++ b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/MonotonicStack.java
@@ -0,0 +1,96 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Problems 4 to 7: the monotonic stack pattern. The stack holds indices whose answer is not known yet, and the
+ * values at those indices never increase from bottom to top. A new value that breaks that order settles every
+ * index it is larger than.
+ */
+public final class MonotonicStack {
+ private MonotonicStack() {}
+
+ /** Problem 4: for each element, the first strictly greater element to its right, or -1. */
+ public static int[] nextGreater(int[] a) {
+ int[] result = new int[a.length];
+ Arrays.fill(result, -1);
+ Deque pending = new ArrayDeque<>();
+ for (int i = 0; i < a.length; i++) {
+ while (!pending.isEmpty() && a[pending.peek()] < a[i]) {
+ result[pending.pop()] = a[i];
+ }
+ pending.push(i);
+ }
+ return result;
+ }
+
+ /** Problem 4, interview form: for each value of nums1 (a subset of nums2, all distinct), its next greater in nums2. */
+ public static int[] nextGreaterSubset(int[] nums1, int[] nums2) {
+ Map nextOf = new HashMap<>();
+ Deque pending = new ArrayDeque<>();
+ for (int x : nums2) {
+ while (!pending.isEmpty() && pending.peek() < x) {
+ nextOf.put(pending.pop(), x);
+ }
+ pending.push(x);
+ }
+ int[] answer = new int[nums1.length];
+ for (int k = 0; k < nums1.length; k++) {
+ answer[k] = nextOf.getOrDefault(nums1[k], -1);
+ }
+ return answer;
+ }
+
+ /** Problem 5: for each day, how many days until a warmer one; 0 if none. */
+ public static int[] dailyTemperatures(int[] temps) {
+ int[] wait = new int[temps.length];
+ Deque pending = new ArrayDeque<>();
+ for (int i = 0; i < temps.length; i++) {
+ while (!pending.isEmpty() && temps[pending.peek()] < temps[i]) {
+ int day = pending.pop();
+ wait[day] = i - day;
+ }
+ pending.push(i);
+ }
+ return wait;
+ }
+
+ /** Problem 6: next greater element in a circular array, so the search may wrap around past the end. */
+ public static int[] nextGreaterCircular(int[] a) {
+ int n = a.length;
+ int[] result = new int[n];
+ Arrays.fill(result, -1);
+ Deque pending = new ArrayDeque<>();
+ for (int i = 0; i < 2 * n; i++) {
+ int value = a[i % n];
+ while (!pending.isEmpty() && a[pending.peek()] < value) {
+ result[pending.pop()] = value;
+ }
+ if (i < n) {
+ pending.push(i); // second lap only settles answers, it never adds new work
+ }
+ }
+ return result;
+ }
+
+ /** Problem 7: area of the largest rectangle inside a histogram. The stack holds indices of rising bar heights. */
+ public static int largestRectangle(int[] heights) {
+ int n = heights.length;
+ int best = 0;
+ Deque rising = new ArrayDeque<>();
+ for (int i = 0; i <= n; i++) {
+ int current = (i == n) ? 0 : heights[i]; // a final bar of height 0 flushes the stack
+ while (!rising.isEmpty() && heights[rising.peek()] >= current) {
+ int height = heights[rising.pop()];
+ int leftEdge = rising.isEmpty() ? -1 : rising.peek();
+ best = Math.max(best, height * (i - leftEdge - 1));
+ }
+ rising.push(i);
+ }
+ return best;
+ }
+}
diff --git a/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/Parentheses.java b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/Parentheses.java
new file mode 100644
index 0000000..c0c18b3
--- /dev/null
+++ b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/Parentheses.java
@@ -0,0 +1,55 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+/** Problem 1: balanced brackets. */
+public final class Parentheses {
+ private Parentheses() {}
+
+ /**
+ * True when every bracket is closed by the matching kind, in the right order. Characters that are not
+ * brackets are ignored. The stack holds the closer each open bracket is waiting for.
+ */
+ public static boolean isBalanced(String s) {
+ Deque expected = new ArrayDeque<>();
+ for (int i = 0; i < s.length(); i++) {
+ char ch = s.charAt(i);
+ switch (ch) {
+ case '(' -> expected.push(')');
+ case '{' -> expected.push('}');
+ case '[' -> expected.push(']');
+ case ')', '}', ']' -> {
+ if (expected.isEmpty() || expected.pop() != ch) {
+ return false;
+ }
+ }
+ default -> { }
+ }
+ }
+ return expected.isEmpty();
+ }
+
+ /**
+ * Index of the first offending character, or -1 when the string is balanced. That is either a closer
+ * with no matching opener, or, when the scan ends with openers left over, the earliest opener never closed.
+ */
+ public static int firstMismatch(String s) {
+ Deque openers = new ArrayDeque<>();
+ for (int i = 0; i < s.length(); i++) {
+ char ch = s.charAt(i);
+ if (ch == '(' || ch == '{' || ch == '[') {
+ openers.push(i);
+ } else if (ch == ')' || ch == '}' || ch == ']') {
+ if (openers.isEmpty() || !matches(s.charAt(openers.pop()), ch)) {
+ return i;
+ }
+ }
+ }
+ return openers.isEmpty() ? -1 : openers.peekLast();
+ }
+
+ private static boolean matches(char open, char close) {
+ return (open == '(' && close == ')') || (open == '{' && close == '}') || (open == '[' && close == ']');
+ }
+}
diff --git a/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/Pitfalls.java b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/Pitfalls.java
new file mode 100644
index 0000000..382c32e
--- /dev/null
+++ b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/Pitfalls.java
@@ -0,0 +1,205 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.NoSuchElementException;
+
+/** Plausible-looking wrong versions. Each one passes a casual test and fails the case the test chooses. */
+public final class Pitfalls {
+ private Pitfalls() {}
+
+ /** Wrong: counts each bracket kind, never checks order. */
+ public static boolean balancedCountsOnly(String s) {
+ int round = 0;
+ int curly = 0;
+ int square = 0;
+ for (int i = 0; i < s.length(); i++) {
+ switch (s.charAt(i)) {
+ case '(' -> round++;
+ case ')' -> round--;
+ case '{' -> curly++;
+ case '}' -> curly--;
+ case '[' -> square++;
+ case ']' -> square--;
+ default -> { }
+ }
+ if (round < 0 || curly < 0 || square < 0) {
+ return false;
+ }
+ }
+ return round == 0 && curly == 0 && square == 0;
+ }
+
+ /** Wrong: compares two boxed Integers with ==, which compares references. Works only while values are cached. */
+ public static final class BoxedEqualsMinStack implements IntMinStack {
+ private final Deque values = new ArrayDeque<>();
+ private final Deque minimums = new ArrayDeque<>();
+
+ @Override
+ public void push(int x) {
+ values.push(x);
+ if (minimums.isEmpty() || x <= minimums.peek()) {
+ minimums.push(x);
+ }
+ }
+
+ @Override
+ public int pop() {
+ Integer x = values.pop();
+ if (x == minimums.peek()) { // Integer == Integer: reference comparison
+ minimums.pop();
+ }
+ return x;
+ }
+
+ @Override
+ public int top() {
+ return values.peek();
+ }
+
+ @Override
+ public int getMin() {
+ return minimums.peek();
+ }
+
+ @Override
+ public int size() {
+ return values.size();
+ }
+ }
+
+ /** Wrong: pushes onto the min stack only on a strictly smaller value, so duplicates of the minimum are lost. */
+ public static final class StrictMinStack implements IntMinStack {
+ private final Deque values = new ArrayDeque<>();
+ private final Deque minimums = new ArrayDeque<>();
+
+ @Override
+ public void push(int x) {
+ values.push(x);
+ if (minimums.isEmpty() || x < minimums.peek()) { // should be <=
+ minimums.push(x);
+ }
+ }
+
+ @Override
+ public int pop() {
+ int x = values.pop();
+ if (x == minimums.peek()) {
+ minimums.pop();
+ }
+ return x;
+ }
+
+ @Override
+ public int top() {
+ return values.peek();
+ }
+
+ @Override
+ public int getMin() {
+ return minimums.peek();
+ }
+
+ @Override
+ public int size() {
+ return values.size();
+ }
+ }
+
+ /** Wrong: the encoded min stack with int differences, which overflow for values far apart. */
+ public static final class IntDiffMinStack implements IntMinStack {
+ private final Deque diffs = new ArrayDeque<>();
+ private int min;
+
+ @Override
+ public void push(int x) {
+ if (diffs.isEmpty()) {
+ diffs.push(0);
+ min = x;
+ return;
+ }
+ diffs.push(x - min); // overflows when x and min are more than Integer.MAX_VALUE apart
+ if (x < min) {
+ min = x;
+ }
+ }
+
+ @Override
+ public int pop() {
+ int diff = diffs.pop();
+ if (diff < 0) {
+ int removed = min;
+ min = min - diff;
+ return removed;
+ }
+ return min + diff;
+ }
+
+ @Override
+ public int top() {
+ int diff = diffs.peek();
+ return diff < 0 ? min : min + diff;
+ }
+
+ @Override
+ public int getMin() {
+ if (diffs.isEmpty()) {
+ throw new NoSuchElementException("stack is empty");
+ }
+ return min;
+ }
+
+ @Override
+ public int size() {
+ return diffs.size();
+ }
+ }
+
+ /** Wrong: pops on {@code <=}, so an equal value is reported as "greater". */
+ public static int[] nextGreaterPopsEqual(int[] a) {
+ int[] result = new int[a.length];
+ Arrays.fill(result, -1);
+ Deque pending = new ArrayDeque<>();
+ for (int i = 0; i < a.length; i++) {
+ while (!pending.isEmpty() && a[pending.peek()] <= a[i]) { // should be <
+ result[pending.pop()] = a[i];
+ }
+ pending.push(i);
+ }
+ return result;
+ }
+
+ /** Wrong: the histogram loop without the final height-0 bar, so bars still on the stack are never measured. */
+ public static int largestRectangleNoSentinel(int[] heights) {
+ int n = heights.length;
+ int best = 0;
+ Deque rising = new ArrayDeque<>();
+ for (int i = 0; i < n; i++) { // should run to i <= n
+ while (!rising.isEmpty() && heights[rising.peek()] >= heights[i]) {
+ int height = heights[rising.pop()];
+ int leftEdge = rising.isEmpty() ? -1 : rising.peek();
+ best = Math.max(best, height * (i - leftEdge - 1));
+ }
+ rising.push(i);
+ }
+ return best;
+ }
+
+ /** Wrong: pours {@code in} onto {@code out} on every dequeue, even when {@code out} still holds older items. */
+ public static final class PourAlwaysQueue {
+ private final Deque in = new ArrayDeque<>();
+ private final Deque out = new ArrayDeque<>();
+
+ public void enqueue(int x) {
+ in.push(x);
+ }
+
+ public int dequeue() {
+ while (!in.isEmpty()) { // should be guarded by out.isEmpty()
+ out.push(in.pop());
+ }
+ return out.pop();
+ }
+ }
+}
diff --git a/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/QueueViaStacks.java b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/QueueViaStacks.java
new file mode 100644
index 0000000..fb93416
--- /dev/null
+++ b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/QueueViaStacks.java
@@ -0,0 +1,58 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.NoSuchElementException;
+
+/**
+ * Problem 9: a FIFO queue built from two LIFO stacks. New items go onto {@code in}. Items leave from {@code out};
+ * when {@code out} is empty, everything in {@code in} is poured over, which reverses it into queue order.
+ */
+public final class QueueViaStacks {
+ private final Deque in = new ArrayDeque<>();
+ private final Deque out = new ArrayDeque<>();
+ private long transfers;
+
+ public void enqueue(T item) {
+ in.push(item);
+ }
+
+ public T dequeue() {
+ pourIfNeeded();
+ if (out.isEmpty()) {
+ throw new NoSuchElementException("queue is empty");
+ }
+ return out.pop();
+ }
+
+ public T peek() {
+ pourIfNeeded();
+ if (out.isEmpty()) {
+ throw new NoSuchElementException("queue is empty");
+ }
+ return out.peek();
+ }
+
+ public int size() {
+ return in.size() + out.size();
+ }
+
+ /** Total number of single-element moves from {@code in} to {@code out} so far. */
+ public long transferCount() {
+ return transfers;
+ }
+
+ /** The two stacks, top first, for traces. */
+ public String snapshot() {
+ return "in=" + in + " out=" + out;
+ }
+
+ private void pourIfNeeded() {
+ if (out.isEmpty()) { // only when out is empty: pouring onto a non-empty out would put new items ahead of old ones
+ while (!in.isEmpty()) {
+ out.push(in.pop());
+ transfers++;
+ }
+ }
+ }
+}
diff --git a/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/Rpn.java b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/Rpn.java
new file mode 100644
index 0000000..67a1e53
--- /dev/null
+++ b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/Rpn.java
@@ -0,0 +1,37 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+/** Problem 2: evaluate a Reverse Polish Notation expression. Division truncates toward zero, as Java's does. */
+public final class Rpn {
+ private Rpn() {}
+
+ public static int evaluate(String[] tokens) {
+ Deque operands = new ArrayDeque<>();
+ for (String token : tokens) {
+ switch (token) {
+ case "+", "-", "*", "/" -> {
+ int right = operands.pop(); // the second operand is on top
+ int left = operands.pop();
+ operands.push(apply(token, left, right));
+ }
+ default -> operands.push(Integer.parseInt(token));
+ }
+ }
+ if (operands.size() != 1) {
+ throw new IllegalArgumentException("malformed expression: " + operands.size() + " values left");
+ }
+ return operands.pop();
+ }
+
+ static int apply(String operator, int left, int right) {
+ return switch (operator) {
+ case "+" -> left + right;
+ case "-" -> left - right;
+ case "*" -> left * right;
+ case "/" -> left / right;
+ default -> throw new IllegalArgumentException("unknown operator " + operator);
+ };
+ }
+}
diff --git a/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/StackViaQueue.java b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/StackViaQueue.java
new file mode 100644
index 0000000..f94200b
--- /dev/null
+++ b/stack-queue/src/main/java/com/ankurm/interviewlab/stackqueue/StackViaQueue.java
@@ -0,0 +1,50 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import java.util.ArrayDeque;
+import java.util.NoSuchElementException;
+import java.util.Queue;
+
+/**
+ * Problem 10: a LIFO stack built from one FIFO queue. After each push, every older item is rotated behind the new
+ * one, so the newest item sits at the front of the queue, which is exactly where pop and top read from.
+ */
+public final class StackViaQueue {
+ private final Queue queue = new ArrayDeque<>();
+ private long rotations;
+
+ public void push(T item) {
+ queue.offer(item);
+ for (int i = queue.size() - 1; i > 0; i--) {
+ queue.offer(queue.poll());
+ rotations++;
+ }
+ }
+
+ public T pop() {
+ if (queue.isEmpty()) {
+ throw new NoSuchElementException("stack is empty");
+ }
+ return queue.poll();
+ }
+
+ public T top() {
+ if (queue.isEmpty()) {
+ throw new NoSuchElementException("stack is empty");
+ }
+ return queue.peek();
+ }
+
+ public int size() {
+ return queue.size();
+ }
+
+ /** Total number of single-element rotations so far. */
+ public long rotationCount() {
+ return rotations;
+ }
+
+ /** The queue, front first (the front is the top of the stack), for traces. */
+ public String snapshot() {
+ return "queue(front=top)=" + queue;
+ }
+}
diff --git a/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/ComplexityTest.java b/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/ComplexityTest.java
new file mode 100644
index 0000000..9272325
--- /dev/null
+++ b/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/ComplexityTest.java
@@ -0,0 +1,85 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.ankurm.interviewlab.stackqueue.Complexity.Counts;
+import com.ankurm.interviewlab.stackqueue.support.Transcript;
+import java.util.Random;
+import org.junit.jupiter.api.Test;
+
+/** Counted work, not timed work: the numbers do not depend on the machine. */
+class ComplexityTest {
+
+ @Test
+ void counts() {
+ Transcript t = Transcript.start("09-operation-counts.txt", "Operation counts (counted, not timed)");
+ Random rnd = new Random(7L);
+
+ t.line("nextGreater on a strictly decreasing array (the worst case for the scan-right version)");
+ t.line(String.format("%8s %18s %18s %14s", "n", "scan-right compares", "stack compares", "stack push+pop"));
+ for (int n : new int[] {1_000, 10_000, 100_000}) {
+ int[] a = new int[n];
+ for (int i = 0; i < n; i++) {
+ a[i] = n - i;
+ }
+ long brute = Complexity.bruteForceComparisons(a);
+ int[] answers = new int[n];
+ Counts stack = Complexity.monotonicNextGreater(a, answers);
+ assertArrayEquals(MonotonicStack.nextGreater(a), answers);
+ assertEquals((long) n * (n - 1) / 2, brute);
+ assertTrue(stack.stackOps() <= 2L * n);
+ t.line(String.format("%8d %18d %18d %14d", n, brute, stack.comparisons(), stack.stackOps()));
+ }
+ t.blank();
+
+ t.line("nextGreater on random values (n = 100000): every index is pushed once and popped at most once");
+ int n = 100_000;
+ int[] r = new int[n];
+ for (int i = 0; i < n; i++) {
+ r[i] = rnd.nextInt(1_000_000);
+ }
+ int[] answers = new int[n];
+ Counts stack = Complexity.monotonicNextGreater(r, answers);
+ assertArrayEquals(MonotonicStack.nextGreater(r), answers);
+ assertEquals(n, stack.pushes());
+ assertTrue(stack.pops() <= n);
+ assertTrue(stack.comparisons() <= 2L * n);
+ t.line("pushes = " + stack.pushes() + ", pops = " + stack.pops() + ", comparisons = " + stack.comparisons()
+ + " (bound: comparisons <= 2n = " + 2L * n + ")");
+ t.blank();
+
+ t.line("QueueViaStacks: n enqueues followed by n dequeues");
+ t.line(String.format("%8s %22s %24s", "n", "total element moves", "moves in the 1st dequeue"));
+ for (int size : new int[] {1_000, 10_000, 100_000}) {
+ QueueViaStacks q = new QueueViaStacks<>();
+ for (int i = 0; i < size; i++) {
+ q.enqueue(i);
+ }
+ q.dequeue();
+ long firstDequeue = q.transferCount();
+ for (int i = 1; i < size; i++) {
+ q.dequeue();
+ }
+ assertEquals(size, firstDequeue);
+ assertEquals(size, q.transferCount());
+ t.line(String.format("%8d %22d %24d", size, q.transferCount(), firstDequeue));
+ }
+ t.line("one dequeue can cost n moves, but each element moves exactly once, so n enqueues + n dequeues cost 3n steps in all");
+ t.blank();
+
+ t.line("StackViaQueue: n pushes");
+ t.line(String.format("%8s %20s %14s", "n", "total rotations", "n(n-1)/2"));
+ for (int size : new int[] {100, 1_000, 5_000}) {
+ StackViaQueue s = new StackViaQueue<>();
+ for (int i = 0; i < size; i++) {
+ s.push(i);
+ }
+ long expected = (long) size * (size - 1) / 2;
+ assertEquals(expected, s.rotationCount());
+ t.line(String.format("%8d %20d %14d", size, s.rotationCount(), expected));
+ }
+ t.flush();
+ }
+}
diff --git a/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/DryRunTest.java b/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/DryRunTest.java
new file mode 100644
index 0000000..be6299f
--- /dev/null
+++ b/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/DryRunTest.java
@@ -0,0 +1,152 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.ankurm.interviewlab.stackqueue.DryRuns.Trace;
+import com.ankurm.interviewlab.stackqueue.support.Transcript;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+/** Writes the step-by-step traces and asserts each instrumented copy agrees with the real method. */
+class DryRunTest {
+
+ @Test
+ void balancedParenthesesTrace() {
+ Transcript t = Transcript.start("01-balanced-parentheses-trace.txt", "Balanced brackets: every step of four inputs");
+ for (String input : new String[] {"{[()]}", "([)]", "())", "(()"}) {
+ Trace trace = DryRuns.balanced(input);
+ assertEquals(Parentheses.isBalanced(input), trace.result());
+ t.line("input: " + input + " firstMismatch = " + Parentheses.firstMismatch(input));
+ trace.lines().forEach(l -> t.line(" " + l));
+ t.blank();
+ }
+ assertTrue(Parentheses.isBalanced("{[()]}"));
+ assertFalse(Parentheses.isBalanced("([)]"));
+ assertEquals(2, Parentheses.firstMismatch("([)]"));
+ assertEquals(2, Parentheses.firstMismatch("())"));
+ assertEquals(0, Parentheses.firstMismatch("(()"));
+ t.flush();
+ }
+
+ @Test
+ void rpnTrace() {
+ Transcript t = Transcript.start("02-rpn-trace.txt", "Reverse Polish Notation: stack after every token");
+ String[][] cases = {{"2", "1", "+", "3", "*"}, {"4", "13", "5", "/", "+"}};
+ int[] expected = {9, 6};
+ for (int c = 0; c < cases.length; c++) {
+ Trace trace = DryRuns.rpn(cases[c]);
+ assertEquals(expected[c], trace.result());
+ assertEquals(expected[c], Rpn.evaluate(cases[c]));
+ t.line("tokens: " + Arrays.toString(cases[c]) + " result = " + trace.result());
+ trace.lines().forEach(l -> t.line(" " + l));
+ t.blank();
+ }
+ t.flush();
+ }
+
+ @Test
+ void minStackTrace() {
+ Transcript t = Transcript.start("03-min-stack-trace.txt", "Min stack: both internal stacks after every operation");
+ String[] ops = {"push 5", "push 3", "push 7", "push 3", "getMin", "pop", "getMin", "pop", "getMin", "pop", "getMin"};
+ Trace> trace = DryRuns.minStack(ops);
+ assertEquals(List.of(3, 3, 3, 5), trace.result());
+ // the same operations on both real implementations
+ for (IntMinStack real : new IntMinStack[] {new MinStack(), new EncodedMinStack()}) {
+ java.util.ArrayList seen = new java.util.ArrayList<>();
+ for (String op : ops) {
+ if (op.startsWith("push ")) {
+ real.push(Integer.parseInt(op.substring(5)));
+ } else if (op.equals("pop")) {
+ real.pop();
+ } else {
+ seen.add(real.getMin());
+ }
+ }
+ assertEquals(trace.result(), seen);
+ }
+ trace.lines().forEach(t::line);
+ t.blank().line("getMin results: " + trace.result() + " (identical for MinStack and EncodedMinStack)");
+ t.flush();
+ }
+
+ @Test
+ void monotonicStackTrace() {
+ Transcript t = Transcript.start("04-monotonic-stack-trace.txt", "Next greater element: the stack of pending indices, step by step");
+ int[] a = {2, 1, 2, 4, 3};
+ Trace trace = DryRuns.nextGreater(a);
+ assertArrayEquals(new int[] {4, 2, 4, -1, -1}, trace.result());
+ assertArrayEquals(MonotonicStack.nextGreater(a), trace.result());
+ t.line("input " + Arrays.toString(a));
+ trace.lines().forEach(l -> t.line(" " + l));
+ t.line("result " + Arrays.toString(trace.result())).blank();
+
+ int[] temps = {73, 74, 75, 71, 69, 72, 76, 73};
+ int[] wait = MonotonicStack.dailyTemperatures(temps);
+ assertArrayEquals(new int[] {1, 1, 4, 2, 1, 1, 0, 0}, wait);
+ t.line("dailyTemperatures " + Arrays.toString(temps));
+ t.line(" = " + Arrays.toString(wait)).blank();
+
+ int[] circle = {1, 2, 1};
+ assertArrayEquals(new int[] {2, -1, 2}, MonotonicStack.nextGreaterCircular(circle));
+ t.line("nextGreaterCircular " + Arrays.toString(circle) + " = " + Arrays.toString(MonotonicStack.nextGreaterCircular(circle)));
+ assertArrayEquals(new int[] {-1, 3, -1}, MonotonicStack.nextGreaterSubset(new int[] {4, 1, 2}, new int[] {1, 3, 4, 2}));
+ t.line("nextGreaterSubset [4, 1, 2] within [1, 3, 4, 2] = "
+ + Arrays.toString(MonotonicStack.nextGreaterSubset(new int[] {4, 1, 2}, new int[] {1, 3, 4, 2})));
+ t.flush();
+ }
+
+ @Test
+ void histogramTrace() {
+ Transcript t = Transcript.start("05-histogram-trace.txt", "Largest rectangle in a histogram: pops, widths and areas");
+ int[] h = {2, 1, 5, 6, 2, 3};
+ Trace trace = DryRuns.histogram(h);
+ assertEquals(10, trace.result());
+ assertEquals(10, MonotonicStack.largestRectangle(h));
+ t.line("heights " + Arrays.toString(h));
+ trace.lines().forEach(l -> t.line(" " + l));
+ t.line("largest rectangle = " + trace.result());
+ t.flush();
+ }
+
+ @Test
+ void queueAndStackConversionTrace() {
+ Transcript t = Transcript.start("06-queue-stack-conversions-trace.txt", "Queue from two stacks, and stack from one queue");
+ QueueViaStacks q = new QueueViaStacks<>();
+ t.line("QueueViaStacks (stacks shown top first)");
+ q.enqueue(1);
+ t.line(" enqueue(1) " + q.snapshot());
+ q.enqueue(2);
+ t.line(" enqueue(2) " + q.snapshot());
+ q.enqueue(3);
+ t.line(" enqueue(3) " + q.snapshot());
+ int a = q.dequeue();
+ t.line(" dequeue() = " + a + " " + q.snapshot() + " transfers so far: " + q.transferCount());
+ q.enqueue(4);
+ t.line(" enqueue(4) " + q.snapshot());
+ int b = q.dequeue();
+ int c = q.dequeue();
+ int d = q.dequeue();
+ t.line(" dequeue() x3 = " + b + ", " + c + ", " + d + " " + q.snapshot() + " transfers so far: " + q.transferCount());
+ assertEquals(List.of(1, 2, 3, 4), List.of(a, b, c, d));
+ assertEquals(4, q.transferCount());
+ t.blank();
+
+ StackViaQueue s = new StackViaQueue<>();
+ t.line("StackViaQueue");
+ for (int x = 1; x <= 3; x++) {
+ s.push(x);
+ t.line(" push(" + x + ") " + s.snapshot() + " rotations so far: " + s.rotationCount());
+ }
+ int p1 = s.pop();
+ int p2 = s.pop();
+ t.line(" pop() x2 = " + p1 + ", " + p2 + " " + s.snapshot());
+ assertEquals(3, p1);
+ assertEquals(2, p2);
+ assertEquals(3, s.rotationCount());
+ t.flush();
+ }
+}
diff --git a/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/JdkStackTest.java b/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/JdkStackTest.java
new file mode 100644
index 0000000..6fb9b34
--- /dev/null
+++ b/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/JdkStackTest.java
@@ -0,0 +1,51 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.ankurm.interviewlab.stackqueue.support.Transcript;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+/** Stack versus ArrayDeque, from reflection and from running both. */
+class JdkStackTest {
+
+ @Test
+ void stackVersusArrayDeque() throws Exception {
+ Transcript t = Transcript.start("10-stack-vs-arraydeque.txt", "java.util.Stack versus ArrayDeque used as a stack");
+
+ assertEquals(List.of(3, 2, 1), JdkStackDemo.stackPopOrder());
+ assertEquals(List.of(1, 2, 3), JdkStackDemo.queuePollOrder());
+ t.line("ArrayDeque as a stack: push(1), push(2), push(3), then pop until empty -> " + JdkStackDemo.stackPopOrder());
+ t.line("ArrayDeque as a queue: offer(1), offer(2), offer(3), then poll until empty -> " + JdkStackDemo.queuePollOrder());
+ t.blank();
+
+ assertEquals(List.of(1, 2, 3), JdkStackDemo.legacyStackOrder());
+ assertEquals(List.of(3, 2, 1), JdkStackDemo.dequeOrder());
+ t.line("push(1), push(2), push(3), then iterate:");
+ t.line(" java.util.Stack -> " + JdkStackDemo.legacyStackOrder() + " (bottom to top)");
+ t.line(" ArrayDeque -> " + JdkStackDemo.dequeOrder() + " (top to bottom)");
+ t.blank();
+
+ assertEquals("java.util.Vector", JdkStackDemo.legacySuperclass());
+ assertTrue(JdkStackDemo.legacyPopIsSynchronized());
+ assertFalse(JdkStackDemo.dequePopIsSynchronized());
+ t.line("Stack.class.getSuperclass() = " + JdkStackDemo.legacySuperclass());
+ t.line("Stack.pop() is synchronized = " + JdkStackDemo.legacyPopIsSynchronized());
+ t.line("ArrayDeque.pop() is synchronized = " + JdkStackDemo.dequePopIsSynchronized());
+ t.blank();
+
+ assertEquals("accepted", JdkStackDemo.pushNullOnLegacy());
+ assertEquals("java.lang.NullPointerException", JdkStackDemo.pushNullOnDeque());
+ t.line("push(null) on Stack = " + JdkStackDemo.pushNullOnLegacy());
+ t.line("push(null) on ArrayDeque = " + JdkStackDemo.pushNullOnDeque());
+ t.blank();
+
+ assertEquals(2, JdkStackDemo.peekAfterPushes());
+ assertEquals(1, JdkStackDemo.peekAfterOffers());
+ t.line("ArrayDeque: push(1), push(2), peek() = " + JdkStackDemo.peekAfterPushes() + " (push is addFirst)");
+ t.line("ArrayDeque: offer(1), offer(2), peek() = " + JdkStackDemo.peekAfterOffers() + " (offer is addLast)");
+ t.flush();
+ }
+}
diff --git a/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/PitfallsTest.java b/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/PitfallsTest.java
new file mode 100644
index 0000000..3533b69
--- /dev/null
+++ b/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/PitfallsTest.java
@@ -0,0 +1,121 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.ankurm.interviewlab.stackqueue.support.Transcript;
+import java.util.Arrays;
+import org.junit.jupiter.api.Test;
+
+/** Eight classic mistakes, each reproduced next to the correct answer. */
+class PitfallsTest {
+
+ @Test
+ void pitfalls() {
+ Transcript t = Transcript.start("11-pitfalls.txt", "Eight classic mistakes, each reproduced");
+
+ assertTrue(Pitfalls.balancedCountsOnly("([)]"));
+ assertFalse(Parentheses.isBalanced("([)]"));
+ t.line("1. balanced brackets by counting each kind, with no stack");
+ t.line(" input \"([)]\": counts-only = " + Pitfalls.balancedCountsOnly("([)]") + " stack = " + Parentheses.isBalanced("([)]"));
+ t.line(" every count ends at zero, but the ] arrives while the ( is not the innermost open bracket");
+ t.blank();
+
+ t.line("2. min stack that compares boxed Integers with ==");
+ for (int[] values : new int[][] {{10, 20, 5}, {1000, 2000, 500}}) {
+ Pitfalls.BoxedEqualsMinStack broken = new Pitfalls.BoxedEqualsMinStack();
+ MinStack right = new MinStack();
+ for (int v : values) {
+ broken.push(v);
+ right.push(v);
+ }
+ broken.pop();
+ right.pop();
+ t.line(" push " + Arrays.toString(values) + ", pop, getMin(): broken = " + broken.getMin() + " correct = " + right.getMin());
+ }
+ t.line(" Integer.valueOf caches -128..127, so small values share one object and == happens to work");
+ t.blank();
+
+ Pitfalls.StrictMinStack strict = new Pitfalls.StrictMinStack();
+ strict.push(3);
+ strict.push(3);
+ strict.pop();
+ String failure;
+ try {
+ strict.getMin();
+ failure = "no exception";
+ } catch (NullPointerException e) {
+ failure = "NullPointerException: " + e.getMessage();
+ }
+ assertTrue(failure.startsWith("NullPointerException"));
+ MinStack good = new MinStack();
+ good.push(3);
+ good.push(3);
+ good.pop();
+ assertEquals(3, good.getMin());
+ t.line("3. min stack that pushes onto the min stack only on a strictly smaller value");
+ t.line(" push 3, push 3, pop, getMin(): strict -> " + failure);
+ t.line(" correct -> " + good.getMin() + " (the stack still holds one 3)");
+ t.blank();
+
+ Pitfalls.IntDiffMinStack overflow = new Pitfalls.IntDiffMinStack();
+ EncodedMinStack encoded = new EncodedMinStack();
+ for (int v : new int[] {Integer.MIN_VALUE, Integer.MAX_VALUE}) {
+ overflow.push(v);
+ encoded.push(v);
+ }
+ assertEquals(Integer.MIN_VALUE, overflow.top());
+ assertEquals(Integer.MAX_VALUE, encoded.top());
+ t.line("4. encoded min stack with int differences instead of long");
+ t.line(" push Integer.MIN_VALUE, push Integer.MAX_VALUE, top(): int diffs = " + overflow.top() + " long diffs = " + encoded.top());
+ t.line(" MAX_VALUE - MIN_VALUE does not fit in an int, wraps to -1, and -1 looks like \"new minimum\"");
+ t.blank();
+
+ int[] dup = {2, 2, 3};
+ assertArrayEquals(new int[] {2, 3, -1}, Pitfalls.nextGreaterPopsEqual(dup));
+ assertArrayEquals(new int[] {3, 3, -1}, MonotonicStack.nextGreater(dup));
+ t.line("5. next greater element popping on <= instead of <");
+ t.line(" input " + Arrays.toString(dup) + ": buggy = " + Arrays.toString(Pitfalls.nextGreaterPopsEqual(dup))
+ + " correct = " + Arrays.toString(MonotonicStack.nextGreater(dup)));
+ t.line(" the first 2 is told its next greater element is 2");
+ t.blank();
+
+ int[] bars = {2, 4};
+ assertEquals(0, Pitfalls.largestRectangleNoSentinel(bars));
+ assertEquals(4, MonotonicStack.largestRectangle(bars));
+ t.line("6. largest rectangle without the final height-0 bar");
+ t.line(" input " + Arrays.toString(bars) + ": buggy = " + Pitfalls.largestRectangleNoSentinel(bars)
+ + " correct = " + MonotonicStack.largestRectangle(bars));
+ t.line(" nothing ever pops the bars that are still on the stack when the loop ends");
+ t.blank();
+
+ Pitfalls.PourAlwaysQueue broken = new Pitfalls.PourAlwaysQueue();
+ QueueViaStacks right = new QueueViaStacks<>();
+ broken.enqueue(1);
+ broken.enqueue(2);
+ right.enqueue(1);
+ right.enqueue(2);
+ int b1 = broken.dequeue();
+ int r1 = right.dequeue();
+ broken.enqueue(3);
+ right.enqueue(3);
+ int b2 = broken.dequeue();
+ int r2 = right.dequeue();
+ assertEquals(1, b1);
+ assertEquals(1, r1);
+ assertEquals(3, b2);
+ assertEquals(2, r2);
+ t.line("7. queue from two stacks that pours on every dequeue");
+ t.line(" enqueue 1, 2; dequeue; enqueue 3; dequeue: first = " + b1 + " (ok), second: broken = " + b2 + " correct = " + r2);
+ t.line(" the 3 was poured on top of the 2 that was still waiting in the output stack");
+ t.blank();
+
+ assertThrows(java.util.NoSuchElementException.class, () -> new QueueViaStacks().dequeue());
+ assertThrows(java.util.NoSuchElementException.class, () -> new StackViaQueue().pop());
+ t.line("8. popping an empty structure: both conversions throw NoSuchElementException, as ArrayDeque.pop does");
+ t.flush();
+ }
+}
diff --git a/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/RandomAgreementTest.java b/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/RandomAgreementTest.java
new file mode 100644
index 0000000..2d59920
--- /dev/null
+++ b/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/RandomAgreementTest.java
@@ -0,0 +1,183 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import com.ankurm.interviewlab.stackqueue.support.Transcript;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.Random;
+import org.junit.jupiter.api.Test;
+
+/** Every stack solution against its slow reference, on seeded random inputs. */
+class RandomAgreementTest {
+ private static final int CASES = 5_000;
+
+ @Test
+ void agreement() {
+ Random rnd = new Random(20261112L);
+ Transcript t = Transcript.start("08-random-agreement.txt",
+ "Random agreement with brute force (seed 20261112, " + CASES + " cases per row)");
+
+ for (int c = 0; c < CASES; c++) {
+ int[] a = randomArray(rnd, rnd.nextInt(13), 6); // small value range forces many duplicates
+ assertArrayEquals(BruteForce.nextGreater(a), MonotonicStack.nextGreater(a));
+ assertArrayEquals(BruteForce.dailyTemperatures(a), MonotonicStack.dailyTemperatures(a));
+ assertArrayEquals(BruteForce.nextGreaterCircular(a), MonotonicStack.nextGreaterCircular(a));
+ assertEquals(BruteForce.largestRectangle(a), MonotonicStack.largestRectangle(a));
+ }
+ t.line(String.format("%-52s %d cases, 0 mismatches", "nextGreater vs scan-right", CASES));
+ t.line(String.format("%-52s %d cases, 0 mismatches", "dailyTemperatures vs scan-right", CASES));
+ t.line(String.format("%-52s %d cases, 0 mismatches", "nextGreaterCircular vs wrap-around scan", CASES));
+ t.line(String.format("%-52s %d cases, 0 mismatches", "largestRectangle vs widen-each-bar", CASES));
+
+ String alphabet = "()[]{}ab";
+ for (int c = 0; c < CASES; c++) {
+ StringBuilder sb = new StringBuilder();
+ int len = rnd.nextInt(13);
+ for (int i = 0; i < len; i++) {
+ sb.append(alphabet.charAt(rnd.nextInt(alphabet.length())));
+ }
+ String s = sb.toString();
+ assertEquals(BruteForce.isBalanced(s), Parentheses.isBalanced(s), s);
+ assertEquals(Parentheses.isBalanced(s), Parentheses.firstMismatch(s) == -1, s);
+ }
+ t.line(String.format("%-52s %d cases, 0 mismatches", "isBalanced vs delete-adjacent-pairs", CASES));
+
+ for (int c = 0; c < CASES; c++) {
+ String encoded = randomEncoded(rnd, 3);
+ assertEquals(BruteForce.decode(encoded), DecodeString.decode(encoded), encoded);
+ }
+ t.line(String.format("%-52s %d cases, 0 mismatches", "decode vs recursive descent", CASES));
+
+ for (int c = 0; c < CASES; c++) {
+ Expr e = randomExpr(rnd, 4);
+ List tokens = new ArrayList<>();
+ e.toRpn(tokens);
+ assertEquals(e.eval(), Rpn.evaluate(tokens.toArray(new String[0])), tokens.toString());
+ }
+ t.line(String.format("%-52s %d cases, 0 mismatches", "evaluate (RPN) vs expression-tree evaluation", CASES));
+
+ int minStackOps = 0;
+ for (int c = 0; c < CASES; c++) {
+ IntMinStack[] impls = {new MinStack(), new EncodedMinStack()};
+ List reference = new ArrayList<>();
+ for (int step = 0; step < 30; step++) {
+ if (reference.isEmpty() || rnd.nextInt(3) > 0) {
+ int x = rnd.nextBoolean() ? rnd.nextInt(2000) - 1000 : rnd.nextInt();
+ reference.add(x);
+ for (IntMinStack s : impls) {
+ s.push(x);
+ }
+ } else {
+ int expected = reference.remove(reference.size() - 1);
+ for (IntMinStack s : impls) {
+ assertEquals(expected, s.pop());
+ }
+ }
+ minStackOps++;
+ if (!reference.isEmpty()) {
+ int min = reference.stream().mapToInt(Integer::intValue).min().getAsInt();
+ int top = reference.get(reference.size() - 1);
+ for (IntMinStack s : impls) {
+ assertEquals(min, s.getMin());
+ assertEquals(top, s.top());
+ assertEquals(reference.size(), s.size());
+ }
+ }
+ }
+ }
+ t.line(String.format("%-52s %d sequences (%d operations), 0 mismatches", "MinStack and EncodedMinStack vs recompute-the-min", CASES, minStackOps));
+
+ long queueOps = 0;
+ for (int c = 0; c < CASES; c++) {
+ QueueViaStacks q = new QueueViaStacks<>();
+ StackViaQueue st = new StackViaQueue<>();
+ Deque refQueue = new ArrayDeque<>();
+ Deque refStack = new ArrayDeque<>();
+ for (int step = 0; step < 30; step++) {
+ if (refQueue.isEmpty() || rnd.nextInt(5) < 3) {
+ int x = rnd.nextInt(100);
+ q.enqueue(x);
+ refQueue.offer(x);
+ st.push(x);
+ refStack.push(x);
+ } else {
+ assertEquals(refQueue.poll(), q.dequeue());
+ assertEquals(refStack.pop(), st.pop());
+ }
+ queueOps++;
+ assertEquals(refQueue.size(), q.size());
+ assertEquals(refStack.size(), st.size());
+ }
+ }
+ t.line(String.format("%-52s %d sequences (%d operations), 0 mismatches", "QueueViaStacks and StackViaQueue vs ArrayDeque", CASES, queueOps));
+ t.flush();
+ }
+
+ private static int[] randomArray(Random rnd, int length, int bound) {
+ int[] a = new int[length];
+ for (int i = 0; i < length; i++) {
+ a[i] = rnd.nextInt(bound);
+ }
+ return a;
+ }
+
+ /** A valid encoded string such as 2[a3[bc]]d, built by chance. */
+ private static String randomEncoded(Random rnd, int depth) {
+ StringBuilder sb = new StringBuilder();
+ int parts = 1 + rnd.nextInt(3);
+ for (int p = 0; p < parts; p++) {
+ if (depth > 0 && rnd.nextInt(3) == 0) {
+ sb.append(1 + rnd.nextInt(3)).append('[').append(randomEncoded(rnd, depth - 1)).append(']');
+ } else {
+ int letters = 1 + rnd.nextInt(2);
+ for (int i = 0; i < letters; i++) {
+ sb.append((char) ('a' + rnd.nextInt(3)));
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ private interface Expr {
+ int eval();
+
+ void toRpn(List out);
+ }
+
+ private static Expr randomExpr(Random rnd, int depth) {
+ if (depth == 0 || rnd.nextInt(4) == 0) {
+ int v = rnd.nextInt(19) - 9;
+ return new Expr() {
+ public int eval() {
+ return v;
+ }
+
+ public void toRpn(List out) {
+ out.add(Integer.toString(v));
+ }
+ };
+ }
+ Expr left = randomExpr(rnd, depth - 1);
+ Expr right = randomExpr(rnd, depth - 1);
+ String op = new String[] {"+", "-", "*", "/"}[rnd.nextInt(4)];
+ if (op.equals("/") && right.eval() == 0) {
+ op = "+"; // never divide by zero
+ }
+ final String chosen = op;
+ return new Expr() {
+ public int eval() {
+ return Rpn.apply(chosen, left.eval(), right.eval());
+ }
+
+ public void toRpn(List out) {
+ left.toRpn(out);
+ right.toRpn(out);
+ out.add(chosen);
+ }
+ };
+ }
+}
diff --git a/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/SolutionsTest.java b/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/SolutionsTest.java
new file mode 100644
index 0000000..b7d2ead
--- /dev/null
+++ b/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/SolutionsTest.java
@@ -0,0 +1,93 @@
+package com.ankurm.interviewlab.stackqueue;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.ankurm.interviewlab.stackqueue.support.Transcript;
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+import org.junit.jupiter.api.Test;
+
+/** One headline example per problem, asserted and written to output/07. */
+class SolutionsTest {
+
+ @Test
+ void allTenProblems() {
+ Transcript t = Transcript.start("07-all-ten-results.txt", "All ten problems on their headline examples");
+
+ assertTrue(Parentheses.isBalanced("{[()]}"));
+ assertFalse(Parentheses.isBalanced("([)]"));
+ t.line(" 1. isBalanced(\"{[()]}\") = " + Parentheses.isBalanced("{[()]}") + "; isBalanced(\"([)]\") = " + Parentheses.isBalanced("([)]"));
+
+ String[] rpn = {"2", "1", "+", "3", "*"};
+ assertEquals(9, Rpn.evaluate(rpn));
+ assertEquals(6, Rpn.evaluate(new String[] {"4", "13", "5", "/", "+"}));
+ assertEquals(-2, Rpn.evaluate(new String[] {"7", "-3", "/"}));
+ t.line(" 2. evaluate(" + Arrays.toString(rpn) + ") = " + Rpn.evaluate(rpn) + "; 7 / -3 truncates toward zero: "
+ + Rpn.evaluate(new String[] {"7", "-3", "/"}));
+
+ MinStack ms = new MinStack();
+ EncodedMinStack es = new EncodedMinStack();
+ for (int x : new int[] {-2, 0, -3}) {
+ ms.push(x);
+ es.push(x);
+ }
+ assertEquals(-3, ms.getMin());
+ assertEquals(-3, es.getMin());
+ ms.pop();
+ es.pop();
+ assertEquals(0, ms.top());
+ assertEquals(0, es.top());
+ assertEquals(-2, ms.getMin());
+ assertEquals(-2, es.getMin());
+ t.line(" 3. push -2, 0, -3: getMin = -3; pop; top = " + ms.top() + ", getMin = " + ms.getMin() + " (both implementations agree)");
+ assertThrows(NoSuchElementException.class, () -> new MinStack().getMin());
+ assertThrows(NoSuchElementException.class, () -> new EncodedMinStack().pop());
+
+ int[] nums = {1, 3, 4, 2};
+ assertArrayEquals(new int[] {3, 4, -1, -1}, MonotonicStack.nextGreater(nums));
+ t.line(" 4. nextGreater(" + Arrays.toString(nums) + ") = " + Arrays.toString(MonotonicStack.nextGreater(nums)));
+
+ int[] temps = {73, 74, 75, 71, 69, 72, 76, 73};
+ assertArrayEquals(new int[] {1, 1, 4, 2, 1, 1, 0, 0}, MonotonicStack.dailyTemperatures(temps));
+ t.line(" 5. dailyTemperatures(" + Arrays.toString(temps) + ") = " + Arrays.toString(MonotonicStack.dailyTemperatures(temps)));
+
+ int[] circle = {1, 2, 3, 4, 3};
+ assertArrayEquals(new int[] {2, 3, 4, -1, 4}, MonotonicStack.nextGreaterCircular(circle));
+ t.line(" 6. nextGreaterCircular(" + Arrays.toString(circle) + ") = " + Arrays.toString(MonotonicStack.nextGreaterCircular(circle)));
+
+ int[] bars = {2, 1, 5, 6, 2, 3};
+ assertEquals(10, MonotonicStack.largestRectangle(bars));
+ assertEquals(4, MonotonicStack.largestRectangle(new int[] {2, 4}));
+ t.line(" 7. largestRectangle(" + Arrays.toString(bars) + ") = " + MonotonicStack.largestRectangle(bars));
+
+ assertEquals("abcbcabcbcabcbc", DecodeString.decode("3[a2[bc]]"));
+ assertEquals("accaccacc", DecodeString.decode("3[a2[c]]"));
+ assertEquals("abcabccdcdcdef", DecodeString.decode("2[abc]3[cd]ef"));
+ t.line(" 8. decode(\"3[a2[bc]]\") = \"" + DecodeString.decode("3[a2[bc]]") + "\"; decode(\"2[abc]3[cd]ef\") = \""
+ + DecodeString.decode("2[abc]3[cd]ef") + "\"");
+
+ QueueViaStacks q = new QueueViaStacks<>();
+ q.enqueue("a");
+ q.enqueue("b");
+ q.enqueue("c");
+ assertEquals("a", q.dequeue());
+ q.enqueue("d");
+ assertEquals("b", q.dequeue());
+ assertEquals("c", q.peek());
+ t.line(" 9. enqueue a, b, c; dequeue = a; enqueue d; dequeue = b; peek = " + q.peek() + "; size = " + q.size());
+
+ StackViaQueue s = new StackViaQueue<>();
+ s.push("a");
+ s.push("b");
+ s.push("c");
+ assertEquals("c", s.top());
+ assertEquals("c", s.pop());
+ assertEquals("b", s.pop());
+ t.line("10. push a, b, c; top = c; pop = c; pop = b; size = " + s.size());
+ t.flush();
+ }
+}
diff --git a/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/support/Transcript.java b/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/support/Transcript.java
new file mode 100644
index 0000000..e66922e
--- /dev/null
+++ b/stack-queue/src/test/java/com/ankurm/interviewlab/stackqueue/support/Transcript.java
@@ -0,0 +1,43 @@
+package com.ankurm.interviewlab.stackqueue.support;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Writes output/NN-*.txt while the calling test asserts the same numbers it prints. */
+public final class Transcript {
+ private final Path path;
+ private final List lines = new ArrayList<>();
+
+ private Transcript(Path path, String header) {
+ this.path = path;
+ lines.add(header);
+ lines.add("=".repeat(Math.min(header.length(), 100)));
+ lines.add("");
+ }
+
+ public static Transcript start(String fileName, String header) {
+ Path dir = Path.of("output");
+ try {
+ Files.createDirectories(dir);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ return new Transcript(dir.resolve(fileName), header);
+ }
+
+ public Transcript line(String text) { lines.add(text); return this; }
+ public Transcript blank() { lines.add(""); return this; }
+
+ public void flush() {
+ try {
+ Files.write(path, (String.join("\n", lines) + "\n").getBytes(StandardCharsets.UTF_8));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+}