Recognizing Stack and Queue Pattern Signals
1. Recognizing Next Greater/Smaller Signal
| Signal | Description | Common Use Cases | Pattern Indicators |
|---|---|---|---|
| Next Greater Element | Find the first element to the right that is greater than the current element. | Stock price analysis, daily temperatures, building heights | Array of numbers with "rightward comparison" |
| Next Smaller Element | Find the first element to the right that is smaller than the current element. | Financial trend analysis, data visualization | Array of numbers with "rightward comparison" |
Example: Next Greater Element
function nextGreaterElement(arr) {
const result = [];
const stack = [];
for (let i = arr.length - 1; i >= 0; i--) {
while (stack.length && stack[stack.length - 1] <= arr[i]) {
stack.pop();
}
if (stack.length === 0) {
result[i] = -1;
} else {
result[i] = stack[stack.length - 1];
}
stack.push(arr[i]);
}
return result;
}
const input = [4, 5, 2, 10, 8];
console.log(nextGreaterElement(input)); // [5, 10, 10, -1, -1]
Example: Next Smaller Element
function nextSmallerElement(arr) {
const result = [];
const stack = [];
for (let i = arr.length - 1; i >= 0; i--) {
while (stack.length && stack[stack.length - 1] >= arr[i]) {
stack.pop();
}
if (stack.length === 0) {
result[i] = -1;
} else {
result[i] = stack[stack.length - 1];
}
stack.push(arr[i]);
}
return result;
}
const input = [4, 5, 2, 10, 8];
console.log(nextSmallerElement(input)); // [2, 2, -1, 8, -1]
Key Indicators for Stack Usage
- Rightward comparison in arrays
- Need to find "next" element satisfying a condition
- Monotonic property (increasing or decreasing) in sequence
2. Recognizing Sliding Window Maximum Signal
| Signal | Description | Indicators |
|---|---|---|
| Fixed-size window | Problem requires processing elements in a fixed-size subset of the input array or list. |
|
| Maximum in window | Need to find the maximum element within each sliding window. |
|
| Dynamic range query | Need to maintain a dynamic range (e.g., max, min) as the window slides. |
|
Example: Sliding Window Maximum
function maxSlidingWindow(nums, k) {
const result = [];
const deque = [];
for (let i = 0; i < nums.length; i++) {
// Remove indices that are out of the current window
while (deque.length && deque[0] <= i - k) {
deque.shift();
}
// Remove indices whose corresponding values are less than nums[i]
while (deque.length && nums[deque[deque.length - 1]] < nums[i]) {
deque.pop();
}
deque.push(i);
// Add the maximum for the current window once we have a full window
if (i >= k - 1) {
result.push(nums[deque[0]]);
}
}
return result;
}
// Example usage:
const nums = [1, 3, -1, -3, 5, 3, 6, 7];
const k = 3;
console.log(maxSlidingWindow(nums, k)); // [3, 3, 5, 5, 6, 7]
Note: The deque-based approach ensures O(n) time complexity by avoiding redundant computations for each window.
3. Recognizing Range Sum Queries Signal
| Signal | Description | Use Case | Pattern Match |
|---|---|---|---|
| Range Sum Query | Need to compute sum of elements in a given range [L, R] | Dynamic array with frequent updates and queries | Query type: sum(L, R) |
| Update Element | Modify value at a specific index | Array with frequent updates and sum queries | Update type: update(index, value) |
Example: Range Sum Query with Segment Tree
class SegmentTree {
constructor(arr) {
this.n = arr.length;
this.tree = new Array(4 * this.n).fill(0);
this.build(arr, 0, 0, this.n - 1);
}
build(arr, node, start, end) {
if (start === end) {
this.tree[node] = arr[start];
} else {
const mid = Math.floor((start + end) / 2);
this.build(arr, 2 * node + 1, start, mid);
this.build(arr, 2 * node + 2, mid + 1, end);
this.tree[node] = this.tree[2 * node + 1] + this.tree[2 * node + 2];
}
}
query(node, start, end, l, r) {
if (r < start || end < l) return 0;
if (l <= start && end <= r) return this.tree[node];
const mid = Math.floor((start + end) / 2);
return this.query(2 * node + 1, start, mid, l, r) +
this.query(2 * node + 2, mid + 1, end, l, r);
}
update(node, start, end, idx, val) {
if (start === end) {
this.tree[node] = val;
} else {
const mid = Math.floor((start + end) / 2);
if (idx <= mid) {
this.update(2 * node + 1, start, mid, idx, val);
} else {
this.update(2 * node + 2, mid + 1, end, idx, val);
}
this.tree[node] = this.tree[2 * node + 1] + this.tree[2 * node + 2];
}
}
}
const arr = [1, 3, 5, 7, 9, 11];
const segTree = new SegmentTree(arr);
console.log(segTree.query(0, 0, 5, 1, 3)); // Output: 15 (3 + 5 + 7)
Note: Range sum queries often indicate the use of a segment tree or Fenwick tree for efficient updates and queries.
4. Recognizing Range Update Queries Signal
| Signal | Description | Typical Use Case | Common Operations |
|---|---|---|---|
| Range Update | Updating values in a range of indices | Dynamic array modifications | updateRange(start, end, value) |
| Range Query | Querying sum, min, max over a range | Frequency counting, statistics | queryRange(start, end) |
| Lazy Propagation | Delaying updates until necessary | Efficient handling of multiple updates | propagate() |
Example: Range Update with Segment Tree
class SegmentTree {
constructor(arr) {
this.n = arr.length;
this.tree = new Array(4 * this.n).fill(0);
this.lazy = new Array(4 * this.n).fill(0);
this.build(arr, 0, 0, this.n - 1);
}
build(arr, node, start, end) {
if (start === end) {
this.tree[node] = arr[start];
} else {
const mid = Math.floor((start + end) / 2);
this.build(arr, 2 * node + 1, start, mid);
this.build(arr, 2 * node + 2, mid + 1, end);
this.tree[node] = this.tree[2 * node + 1] + this.tree[2 * node + 2];
}
}
updateRange(node, start, end, l, r, value) {
if (start > r || end < l) return;
if (start >= l && end <= r) {
this.tree[node] += value * (end - start + 1);
if (start !== end) {
this.lazy[2 * node + 1] += value;
this.lazy[2 * node + 2] += value;
}
return;
}
const mid = Math.floor((start + end) / 2);
this.updateRange(2 * node + 1, start, mid, l, r, value);
this.updateRange(2 * node + 2, mid + 1, end, l, r, value);
this.tree[node] = this.tree[2 * node + 1] + this.tree[2 * node + 2];
}
queryRange(node, start, end, l, r) {
if (start > r || end < l) return 0;
if (start >= l && end <= r) return this.tree[node];
const mid = Math.floor((start + end) / 2);
return this.queryRange(2 * node + 1, start, mid, l, r) +
this.queryRange(2 * node + 2, mid + 1, end, l, r);
}
}
// Usage
const arr = [1, 2, 3, 4, 5];
const segTree = new SegmentTree(arr);
segTree.updateRange(0, 0, arr.length - 1, 1, 3, 5);
console.log(segTree.queryRange(0, 0, arr.length - 1, 0, 2)); // 15
Note: Range update queries often require segment trees or Fenwick trees for efficient handling.
Key Indicators of Range Update Signals
- Multiple updates over ranges
- Need to query aggregated values (sum, min, max) in ranges
- Efficiency is crucial for large datasets
5. Recognizing Frequency/Count/Grouping Signal
| Signal | Description | Common Patterns | Examples |
|---|---|---|---|
| Frequency Count | Need to count occurrences of elements | Hash maps, frequency tables | |
| Grouping Elements | Group items by a property or criteria | Maps, grouping functions | |
| Counting Unique Items | Track unique elements or their counts | Set, Map, frequency tracking | |
Example: Grouping by Frequency
const items = ['a', 'b', 'c', 'a', 'b', 'a'];
const freq = {};
for (let item of items) {
freq[item] = (freq[item] || 0) + 1;
}
// Result: { a: 3, b: 2, c: 1 }
Note: Frequency counting often indicates a need for hash maps or frequency tables to efficiently track element occurrences.
6. Recognizing Valid Parentheses/Brackets Signal
| Pattern | Description | Use Case | Examples |
|---|---|---|---|
| Stack-based validation | Uses a stack to track opening brackets and match them with closing ones | Checking syntax correctness in compilers or parsers | ()[]{}() |
| Mismatch detection | Stack top does not match the current closing bracket | Identifying malformed expressions | ([)] |
| Unbalanced brackets | Stack is not empty after processing all characters | Ensuring balanced expressions in code or math | ((()) |
Example: Valid Parentheses Check
function isValid(s) {
const stack = [];
const map = { ')': '(', ']': '[', '}': '{' };
for (let i = 0; i < s.length; i++) {
const char = s[i];
if (map[char]) {
if (stack.pop() !== map[char]) return false;
} else {
stack.push(char);
}
}
return stack.length === 0;
}
isValid("()[]{}"); // true
isValid("([)]"); // false
Note: The stack ensures last-in-first-out (LIFO) order, which matches the nesting behavior of parentheses.