Stack With a Range-Increment Operation
Solve this Problempush and pop, plus increment(k, val), which adds val to each of the bottom min(k, size) elements currently on the stack.
Applying an increment to potentially many elements at once seems to demand touching each of them — but the actual values only ever matter once an element is popped and revealed. That gap between "when the increment happens" and "when the value is needed" is exploitable: record each increment's effect in exactly one place (a parallel array, at the position marking how far down it reaches), and let that pending amount quietly cascade downward one step at a time, only when a pop makes it necessary. Every operation — push, pop, and increment alike — becomes a fixed, small amount of work, no matter how many elements an increment conceptually covers.
Test Case 1:
Test Case 2:
Test Case 3:
Constraints
- ◆
1 ≤ ops.length ≤ 20 - ◆
ops[i] is one of "push", "pop", "increment"; args[i] is the pushed value (for push) or k (for increment); args2[i] is val (for increment), 0 otherwise - ◆
increment(k, val) adds val to each of the bottom min(k, current size) elements - ◆
"pop" on an empty stack returns -1
Try the Dry Run
Don't just read the solution — watch it execute, one step at a time.
Approach & Solutions
Brute Force — Directly Add to Every Affected Element
BruteUse a plain array-backed stack. push and pop are the usual O(1) operations. For increment(k, val), walk the bottom min(k, size) elements and add val to each one directly. This is completely correct and simple, but every increment call costs time proportional to how many elements it touches — a sequence with many large-k increments pays that cost every single time.
O(1) push/pop, O(k) incrementO(n)1class Solution {
2 public int[] stackOpsIncrement(String[] ops, int[] args, int[] args2) {
3 List<Integer> stack = new ArrayList<>();
4 List<Integer> results = new ArrayList<>();
5 for (int i = 0; i < ops.length; i++) {
6 String op = ops[i];
7 if (op.equals("push")) {
8 stack.add(args[i]);
9 } else if (op.equals("pop")) {
10 results.add(stack.isEmpty() ? -1 : stack.remove(stack.size() - 1));
11 } else {
12 int k = args[i], val = args2[i];
13 int limit = Math.min(k, stack.size());
14 for (int j = 0; j < limit; j++) {
15 stack.set(j, stack.get(j) + val);
16 }
17 }
18 }
19 int[] output = new int[results.size()];
20 for (int i = 0; i < results.size(); i++) output[i] = results.get(i);
21 return output;
22 }
23}Optimal — Lazy Increments Resolved Only at Pop Time
OptimalNever touch min(k, size) elements up front. Instead, keep a second array of pending increments, one slot per stack position. increment(k, val) records its effect in exactly one place — the slot at index k-1 (the boundary of what it affects) — in O(1), without touching anything else. The trick is deferring the actual work: a pending increment at some position secretly also applies to everything below it, so when popping the top element, first push that position's pending amount down onto the position right below it (so it isn't lost), then add the (now-correct) pending amount to the value being returned. Every push, pop, and increment call does a fixed, small amount of work — O(1) across the board.
O(1) for every operationO(n)1class Solution {
2 public int[] stackOpsIncrement(String[] ops, int[] args, int[] args2) {
3 int[] stack = new int[ops.length];
4 int[] inc = new int[ops.length];
5 int top = -1;
6 List<Integer> results = new ArrayList<>();
7 for (int i = 0; i < ops.length; i++) {
8 String op = ops[i];
9 if (op.equals("push")) {
10 top++;
11 stack[top] = args[i];
12 inc[top] = 0;
13 } else if (op.equals("pop")) {
14 if (top < 0) { results.add(-1); }
15 else {
16 if (top > 0) inc[top - 1] += inc[top];
17 results.add(stack[top] + inc[top]);
18 top--;
19 }
20 } else {
21 int k = args[i], val = args2[i];
22 if (top >= 0) {
23 int idx = Math.min(k, top + 1) - 1;
24 inc[idx] += val;
25 }
26 }
27 }
28 int[] output = new int[results.size()];
29 for (int i = 0; i < results.size(); i++) output[i] = results.get(i);
30 return output;
31 }
32}