📚 The "Running Pointer" Philosophy
Core Concept: Instead of iterating to find relationships (which is slow), we FIX one variable (the "Running Pointer") and mathematically deduce what the REMAINING value or structure must be.
1. Fixing the Equation (Map Technique)
The Aim: Solve \(A[i] + A[j] = K\).
When to Use: You need to count pairs satisfying an equality condition.
The Concept: Instead of scanning the array to find a partner, use a Hash Map to remember what you have seen so far.
What we Fix: The current element \(A[i]\) (as we iterate).
What we Calculate: The "Missing Part" required to complete the sum.
The Math: \(A[i] + A[j] = \text{Target} \implies A[j] = \text{Target} - A[i]\)
Code Template:
Click to expand solution code
// Count pairs where A[i] + A[j] == Target
int countPairs(vector<int>& arr, int target) {
unordered_map<int, int> seen;
int ans = 0;
// FIX: The current element 'x'
for(int x : arr) {
int needed = target - x;
// CALCULATE: If 'needed' exists, we found valid pairs
if(seen.count(needed)) {
ans += seen[needed];
}
seen[x]++;
}
return ans;
}
2. Fixing the Edge (Tree Contribution)
The Aim: Count paths that cross a specific connection.
When to Use: Summing path values or counting interactions in a tree.
The Concept: This changes your perspective from "finding pairs" to "calculating participation."
What we Fix: A specific edge \(u \to v\).
What we Calculate: The size of the two worlds created by cutting this edge.
The Math: If you cut an edge, it splits the tree into two components of size \(S\) and \(N-S\). Any path starting in one component and ending in the other must cross this edge. \(\text{Pairs crossing edge} = S \times (N - S)\)
Code Template:
Click to expand solution code
long long totalNodes;
long long ans = 0;
long long dfs(int u, int p, const vector<vector<int>>& adj) {
long long sz = 1;
for (int v : adj[u]) {
if (v == p) continue;
long long childSz = dfs(v, u, adj);
// FIX: The edge between u and v
// CALCULATE: Nodes in subtree * Nodes outside subtree
ans += childSz * (totalNodes - childSz);
sz += childSz;
}
return sz;
}
3. Fixing the Element (Scope Contribution)
The Aim: Sum of Minimums/Maximums of all subarrays. When to Use: Problems like "Sum of Subarray Minimums".
What we Fix: The element \(A[i]\) (assuming it is the minimum). What we Calculate: The "Range of Influence" (\(L\) and \(R\)).
The Math: For a specific element \(X\), it contributes to the answer only for subarrays where it is the relevant value.
- \(L\) = Distance to the previous boundary (e.g., previous smaller element).
- \(R\) = Distance to the next boundary.
\(\text{Total Contribution} = \text{Value} \times L \times R\)
Solution Outline:
- Fix: Element \(A[i]\).
- Calculate: Use a Monotonic Stack to find indices of Previous Less Element and Next Less Element.
- Math:
Ans += A[i] * (i - PLE) * (NLE - i)
C++ Implementation
Click to expand solution code
int sumSubarrayMins(vector<int>& arr) {
int n = arr.size();
vector<int> left(n), right(n);
stack<int> s;
// FIX: Element arr[i] acting as the minimum
// Find Previous Less Element (PLE)
for(int i = 0; i < n; ++i) {
while(!s.empty() && arr[s.top()] >= arr[i]) s.pop();
left[i] = s.empty() ? i + 1 : i - s.top();
s.push(i);
}
while(!s.empty()) s.pop();
// Find Next Less Element (NLE)
for(int i = n - 1; i >= 0; --i) {
while(!s.empty() && arr[s.top()] > arr[i]) s.pop();
right[i] = s.empty() ? n - i : s.top() - i;
s.push(i);
}
long long ans = 0, mod = 1e9 + 7;
for(int i = 0; i < n; ++i) {
// CALCULATE: Contribution = Val * LeftRange * RightRange
long long contribution = (long long)arr[i] * left[i] * right[i];
ans = (ans + contribution) % mod;
}
return ans;
}
4. Fixing the Component (Complement Technique)
The Aim: Count unreachable pairs (nodes not in the same group).
When to Use: The condition for "valid" pairs is complex (e.g., disconnected), but "invalid" pairs (connected) are easy to group.
What we Fix: A single connected component (island).
What we Calculate: The number of nodes outside this island.
The Math:
\(\text{Valid Pairs} = \text{Total Pairs} - \text{Invalid Pairs}\)
- Total Pairs: \(\binom{N}{2} = \frac{N(N-1)}{2}\)
- Invalid Pairs (Reachable): For a component of size \(S\), pairs are \(\frac{S(S-1)}{2}\).
Solution Outline:
- Calculate
Total_Pairs. - Find Connected Components (using DFS/Union-Find).
- For each component of size \(S\), subtract \(\frac{S(S-1)}{2}\) from
Total_Pairs.
C++ Implementation
Click to expand solution code
long long countUnreachable(int n, vector<vector<int>>& edges) {
// Build graph adj...
vector<bool> vis(n, false);
long long totalPairs = (long long)n * (n - 1) / 2;
long long reachablePairs = 0;
for(int i=0; i<n; ++i) {
if(!vis[i]) {
// FIX: A connected component
long long size = 0;
// dfs to get component size...
// CALCULATE: Pairs within this component are "reachable" (bad for us)
reachablePairs += size * (size - 1) / 2;
}
}
return totalPairs - reachablePairs;
}
5. Fixing the Remainder (Running Prefix Sums)
The Aim: Subarray sum divisible by \(K\) (or equal to \(K\)).
When to Use: Transforms a Range problem into a Point problem (Technique #1).
What we Fix: The current prefix sum's remainder Current % K.
What we Calculate: The same remainder seen previously.
The Math:
- Equality: \(P[j] - P[i-1] = K \implies P[i-1] = P[j] - K\).
- Modulo: \((P[j] - P[i-1]) \% K == 0 \implies P[j] \% K == P[i-1] \% K\).
Solution Outline:
- Initialize map
seen = {0: 1}. - Iterate array, calculating
current_sum. - Fix:
rem = current_sum % K. - Calculate:
Ans += seen[rem]. - Store
remin map.
C++ Implementation
Click to expand solution code
int subarraysDivByK(vector<int>& nums, int k) {
unordered_map<int, int> seen = {{0, 1}};
int prefix = 0, ans = 0;
for(int x : nums) {
prefix += x;
// FIX: The remainder of current prefix sum
int mod = ((prefix % k) + k) % k; // Handle negative mods
// CALCULATE: If we saw this mod before, the sub-part is divisible by K
if(seen.count(mod)) ans += seen[mod];
seen[mod]++;
}
return ans;
}
6. Combinatorics (Grouping Identical Elements)
The Aim: Count pairs of identical elements. When to Use: You just need to count pairs where \(A[i] == A[j]\).
What we Fix: A value \(X\). What we Calculate: How many ways to pick 2 items from the count of \(X\).
The Math:
If a value \(X\) appears count times, the number of pairs is:
\(\frac{\text{count} \times (\text{count} - 1)}{2}\)
C++ Implementation
Click to expand solution code
IV. Absolute Value Tricks (Inequalities & Expansions)
Core Concept: Absolute value \(|x|\) creates "cases". To solve efficiently, either expand the cases (\(x\) vs \(-x\)) or treat it as distance on a number line.
1. Minimize Distance from Target (Media.net)
Task: Find the minimum value of abs(abs(subarray_sum) - T) for any subarray
The Hint:
- To minimize \(|X - T|\), \(X\) should be as close to \(T\) (or \(-T\)) as possible.
- Fix: The array (via sorted Prefix Sums).
- Calculate:
Lower_Boundon prefix sums. We wantPrefix[j] - Prefix[i] ≈ T. - Note: We can solve this by maintaining a sorted set of prefix sums and using binary search (
lower_bound) or two pointer to find the closest values tocurrent_prefix - Tandcurrent_prefix + T.
2. The Triplet Constraint (Google)
Task: Given sorted arrays \(A, B, C\) and diff \(D\), count tuples \((i, j, k)\) where all pairwise diffs \(\le D\). The Hint:
- Since arrays are sorted, if you fix \(A[i]\), the valid values in \(B\) and \(C\) form contiguous ranges (windows).
- Simpler Approach: Iterate pointers. If
max - min > D, advance the pointer of the minimum value.
3. Maximize Expression with Indices (LeetCode 1131)
Task: Maximize \(|arr1[i] - arr1[j]| + |arr2[i] - arr2[j]| + |i - j|\).
The Hint:
- \(|x| = \max(x, -x)\).
- Fix: One of the 4 sign combinations (e.g.,
+ + +). - Calculate: Maximize
(A[i] + B[i] + i) - (A[j] + B[j] + j)by finding Max and Min of the transformed array in one pass.
4. Minimize Max Absolute Sum (GeeksForGeeks/General)
Task: Partition array into \(K\) subarrays to minimize the maximum absolute sum. The Hint:
- "Minimize the Maximum" \(\to\) Binary Search on Answer.
- Fix: A candidate limit
X. - Calculate: Can we partition such that no sum exceeds
X? (Greedy check).
C++ Implementation (Example: Minimize Max Absolute Sum)
Click to expand solution code
📝 Practice Set: "What do I fix?"
I. Fix Equation (Maps)
- 1. Two Sum (Fix
x, Calctarget-x) - 560. Subarray Sum Equals K (Fix
Prefix, CalcPrefix - K) - 974. Subarray Sums Divisible by K (Fix
Mod, CalcMod) - 1248. Count Nice Subarrays (Fix
OddCount, CalcOddCount - K) - 2364. Count Bad Pairs (Fix
i - nums[i], Calc Same) - 523. Continuous Subarray Sum (Fix
Mod, Check Index Distance) - 1512. Number of Good Pairs (Fix
Value, CalcFrequency) - 2488. Count Subarrays With Median K (Fix
Balance, CalcInverse Balance) - 1395. Count Number of Teams (Fix
Middle Soldier, CalcSmaller Left * Larger Right) - 167. Two Sum II (Fix
Left, MoveRightto match)
II. Fix Structure (Tree/Edge)
- 1339. Max Product of Splitted Binary Tree (Fix Edge, Calc
Subtree * (Total - Subtree)) - 979. Distribute Coins in Binary Tree (Fix Edge, Calc
Flow = Coins - Nodes) - 834. Sum of Distances in Tree (Fix Node, Calc
Re-rooted Sum) - 2049. Count Nodes With Highest Score (Fix Node, Calc
Prod of Component Sizes) - 1530. Number of Good Leaf Nodes Pairs (Fix LCA, Calc
Left Dist + Right Dist) - 687. Longest Univalue Path (Fix Root, Calc
Left Path + Right Path) - 129. Sum Root to Leaf Numbers (Fix Path, Calc
Val * 10 + Node) - 1448. Count Good Nodes in Binary Tree (Fix Path, Calc
Max So Far) - 2673. Make Costs of Paths Equal (Fix Subtree, Calc
Diff between Children) - 437. Path Sum III (Fix Path End, Calc
Prefix - Target)
III. Fix Group (Graph/Component)
- 2316. Count Unreachable Pairs (Fix Component, Calc
Total - Size) - 1615. Maximal Network Rank (Fix Pair of Cities, Calc
Deg[A] + Deg[B] - SharedEdge) - 1319. Operations to Make Network Connected (Fix Graph, Calc
Components - 1) - 841. Keys and Rooms (Fix Start Room, Calc
Visitable Count) - 547. Number of Provinces (Fix Node, Calc
Connected Component) - 1557. Min Vertices to Reach All Nodes (Fix Node, Calc
In-Degree == 0) - 2492. Min Score of Path (Fix Component, Calc
Min Edge in Component) - 2685. Count Complete Components (Fix Component, Calc
Edges == Size*(Size-1)/2) - 797. All Paths From Source to Target (Fix Path, Calc
Next Step) - 1971. Find if Path Exists (Fix Start, Calc
Reachability to End)
IV. Bonus: Combinatorics & Math
- 62. Unique Paths (Medium) - Grid Math.
- 118. Pascal's Triangle (Easy) - Visualizing nCr.
- 2400. Ways to Reach Position After K Steps (Medium) - Choice Math.
- 1641. Count Sorted Vowel Strings (Medium) - Stars and Bars.
- 1359. Count Valid Pickup and Delivery (Hard) - Permutations.
💻 Solutions to Practice Set
I. Fix Equation (Maps)
1. Two Sum
2. Subarray Sum Equals K
3. Subarray Sums Divisible by K
4. Count Nice Subarrays
int numberOfSubarrays(vector<int>& nums, int k) {
// Transform: Odd->1, Even->0. Problem becomes "Subarray Sum Equals K"
unordered_map<int, int> m = {{0, 1}};
int sum = 0, ans = 0;
for (int x : nums) {
sum += (x % 2);
// FIX: prefix sum of odd counts
// CALCULATE: prefix - k
if (m.count(sum - k)) ans += m[sum - k];
m[sum]++;
}
return ans;
}
5. Count Bad Pairs
long long countBadPairs(vector<int>& nums) {
// Bad: j - i != A[j] - A[i] => A[i] - i != A[j] - j
// Good: A[i] - i == A[j] - j
unordered_map<int, int> m;
long long good = 0, n = nums.size();
for (int i = 0; i < n; ++i) {
int val = nums[i] - i;
// FIX: (nums[i] - i)
// CALCULATE: previous occurrences of same value
if (m.count(val)) good += m[val];
m[val]++;
}
return (n * (n - 1) / 2) - good; // Total - Good
}
6. Continuous Subarray Sum
bool checkSubarraySum(vector<int>& nums, int k) {
unordered_map<int, int> m = {{0, -1}};
int sum = 0;
for (int i = 0; i < nums.size(); ++i) {
sum = (sum + nums[i]) % k;
// FIX: current mod
// CALCULATE: if mod seen before at index prev
if (m.count(sum)) {
if (i - m[sum] > 1) return true; // Ensure length >= 2
} else {
m[sum] = i;
}
}
return false;
}
7. Number of Good Pairs
8. Count Subarrays With Median K
int countSubarrays(vector<int>& nums, int k) {
int p = find(nums.begin(), nums.end(), k) - nums.begin();
unordered_map<int, int> m;
int bal = 0, ans = 0;
// Scan left from K
for (int i = p; i >= 0; --i) {
bal += (nums[i] == k ? 0 : (nums[i] < k ? -1 : 1));
m[bal]++;
}
bal = 0;
// Scan right from K
for (int i = p; i < nums.size(); ++i) {
bal += (nums[i] == k ? 0 : (nums[i] < k ? -1 : 1));
// FIX: right balance
// CALCULATE: exact opposite balance on left (or +1 for even len)
ans += m[-bal] + m[-bal + 1];
}
return ans;
}
9. Count Number of Teams
int numTeams(vector<int>& rating) {
int ans = 0, n = rating.size();
for (int i = 1; i < n - 1; ++i) {
// FIX: middle soldier i
int leftLess = 0, rightGreater = 0, leftGreater = 0, rightLess = 0;
for (int j = 0; j < i; ++j) if (rating[j] < rating[i]) leftLess++; else leftGreater++;
for (int j = i + 1; j < n; ++j) if (rating[j] > rating[i]) rightGreater++; else rightLess++;
// CALCULATE: Combinations (Small-Mid-Large) + (Large-Mid-Small)
ans += leftLess * rightGreater + leftGreater * rightLess;
}
return ans;
}
10. Two Sum II
II. Fix Structure (Tree/Edge)
11. Max Product of Splitted Binary Tree
long long total = 0, ans = 0;
long long sum(TreeNode* root) {
if (!root) return 0;
long long s = root->val + sum(root->left) + sum(root->right);
// FIX: The edge above current subtree
// CALCULATE: product of subtree sum and (total - subtree sum)
if (total != 0) ans = max(ans, s * (total - s));
return s;
}
int maxProduct(TreeNode* root) {
total = sum(root); // First pass to get total
sum(root); // Second pass to find max
return ans % (int)(1e9 + 7);
}
12. Distribute Coins in Binary Tree
int moves = 0;
int dfs(TreeNode* root) {
if (!root) return 0;
int l = dfs(root->left), r = dfs(root->right);
// FIX: Edge connecting root to parent
// CALCULATE: Flow needed = (coins - 1 for self) + left excess + right excess
moves += abs(l) + abs(r);
return root->val - 1 + l + r;
}
int distributeCoins(TreeNode* root) {
dfs(root);
return moves;
}
13. Sum of Distances in Tree
vector<int> ans, count;
int N;
void dfs1(int u, int p, vector<vector<int>>& adj) {
for (int v : adj[u]) {
if (v == p) continue;
dfs1(v, u, adj);
count[u] += count[v];
ans[u] += ans[v] + count[v];
}
}
void dfs2(int u, int p, vector<vector<int>>& adj) {
for (int v : adj[u]) {
if (v == p) continue;
// FIX: The edge between parent and child (re-rooting)
// CALCULATE: New answer based on parent's answer
ans[v] = ans[u] - count[v] + (N - count[v]);
dfs2(v, u, adj);
}
}
// Main function would init vars and call dfs1 then dfs2
14. Count Nodes With Highest Score
long long maxScore = 0;
int cnt = 0, n;
int dfs(int u, vector<vector<int>>& adj) {
int sum = 1;
long long score = 1;
for (int v : adj[u]) {
int child = dfs(v, adj);
score *= child;
sum += child;
}
// FIX: Node u
// CALCULATE: Product of all components formed by removing u
if (n - sum > 0) score *= (n - sum);
if (score > maxScore) { maxScore = score; cnt = 1; }
else if (score == maxScore) cnt++;
return sum;
}
15. Number of Good Leaf Nodes Pairs
int ans = 0;
vector<int> dfs(TreeNode* root, int d) {
if (!root) return {};
if (!root->left && !root->right) return {1};
auto l = dfs(root->left, d), r = dfs(root->right, d);
// FIX: The LCA (current root)
// CALCULATE: Pairs from left and right with dist sum <= d
for (int i : l) for (int j : r) if (i + j <= d) ans++;
vector<int> res;
for (int i : l) if (i + 1 < d) res.push_back(i + 1);
for (int j : r) if (j + 1 < d) res.push_back(j + 1);
return res;
}
16. Longest Univalue Path
int ans = 0;
int dfs(TreeNode* root) {
if (!root) return 0;
int l = dfs(root->left), r = dfs(root->right);
int pl = 0, pr = 0;
if (root->left && root->left->val == root->val) pl = l + 1;
if (root->right && root->right->val == root->val) pr = r + 1;
// FIX: The root as the turning point of path
// CALCULATE: Path through root = left_arm + right_arm
ans = max(ans, pl + pr);
return max(pl, pr);
}
int longestUnivaluePath(TreeNode* root) {
dfs(root);
return ans;
}
17. Sum Root to Leaf Numbers
18. Count Good Nodes in Binary Tree
19. Make Costs of Paths Equal
int minIncrements(int n, vector<int>& cost) {
int ans = 0;
for (int i = n / 2 - 1; i >= 0; --i) {
// FIX: Subtree at i
// CALCULATE: Diff between children needs to be added to answer
int l = cost[2 * i + 1], r = cost[2 * i + 2];
ans += abs(l - r);
cost[i] += max(l, r); // Propagate max up
}
return ans;
}
20. Path Sum III
unordered_map<long, int> map;
int count = 0;
void dfs(TreeNode* root, int target, long curr) {
if (!root) return;
curr += root->val;
// FIX: Path ending at current node
// CALCULATE: Prefix sums that result in target
if (map.count(curr - target)) count += map[curr - target];
map[curr]++;
dfs(root->left, target, curr);
dfs(root->right, target, curr);
map[curr]--; // Backtrack
}
III. Fix Group (Graph/Component)
21. Count Unreachable Pairs
long long countUnreachable(int n, vector<vector<int>>& edges) {
// Build Graph...
vector<bool> vis(n);
long long total = (long long)n * (n - 1) / 2, reachable = 0;
for(int i=0; i<n; ++i) {
if(!vis[i]) {
long long cnt = 0;
// dfs to count nodes in component...
// FIX: Component size
// CALCULATE: Pairs within component (reachable)
reachable += cnt * (cnt - 1) / 2;
}
}
return total - reachable;
}
22. Maximal Network Rank
int maximalNetworkRank(int n, vector<vector<int>>& roads) {
vector<int> deg(n);
vector<vector<bool>> connected(n, vector<bool>(n));
for (auto& r : roads) {
deg[r[0]]++; deg[r[1]]++;
connected[r[0]][r[1]] = connected[r[1]][r[0]] = true;
}
int maxRank = 0;
// FIX: Pair of cities i and j
// CALCULATE: Sum of degrees minus shared edge
for (int i = 0; i < n; ++i)
for (int j = i + 1; j < n; ++j)
maxRank = max(maxRank, deg[i] + deg[j] - connected[i][j]);
return maxRank;
}
23. Operations to Make Network Connected
int makeConnected(int n, vector<vector<int>>& connections) {
if (connections.size() < n - 1) return -1;
// FIX: The Graph
// CALCULATE: Number of connected components (CC)
// Result is CC - 1
int components = 0;
vector<bool> vis(n);
// Standard DFS loop to count components...
return components - 1;
}
24. Keys and Rooms
bool canVisitAllRooms(vector<vector<int>>& rooms) {
int n = rooms.size(), visitedCount = 0;
vector<bool> vis(n);
queue<int> q; q.push(0); vis[0] = true;
while (!q.empty()) {
int u = q.front(); q.pop(); visitedCount++;
// FIX: Current room u
// CALCULATE: Add unvisited neighbors to queue
for (int v : rooms[u]) if (!vis[v]) { vis[v] = true; q.push(v); }
}
return visitedCount == n;
}
25. Number of Provinces
int findCircleNum(vector<vector<int>>& isConnected) {
int n = isConnected.size(), provinces = 0;
vector<bool> vis(n);
for (int i = 0; i < n; ++i) {
if (!vis[i]) {
// FIX: A starting node of a component
// CALCULATE: Traverse entire component, increment count
provinces++;
// DFS/BFS code here...
}
}
return provinces;
}
26. Min Vertices to Reach All Nodes
vector<int> findSmallestSetOfVertices(int n, vector<vector<int>>& edges) {
vector<int> inDegree(n, 0), ans;
for (auto& e : edges) inDegree[e[1]]++;
// FIX: Each Node
// CALCULATE: If in-degree is 0, it must be in the set
for (int i = 0; i < n; ++i) if (inDegree[i] == 0) ans.push_back(i);
return ans;
}
27. Min Score of Path
28. Count Complete Components
int countCompleteComponents(int n, vector<vector<int>>& edges) {
int ans = 0;
vector<bool> vis(n);
// Build graph...
for(int i=0; i<n; ++i) {
if(!vis[i]) {
int nodes = 0, edgeCount = 0;
// DFS: count nodes and edges in this component
// FIX: Component structure
// CALCULATE: if edges == nodes * (nodes - 1) / 2
if (edgeCount / 2 == nodes * (nodes - 1) / 2) ans++;
}
}
return ans;
}
29. All Paths From Source to Target
vector<vector<int>> ans;
void dfs(int u, vector<int>& path, vector<vector<int>>& graph) {
path.push_back(u);
if (u == graph.size() - 1) ans.push_back(path);
else for (int v : graph[u]) dfs(v, path, graph);
path.pop_back(); // Backtrack
}
// FIX: Current path state
// CALCULATE: Extensions to neighbors
30. Find if Path Exists
IV. Bonus: Combinatorics & Math
31. Unique Paths
32. Pascal's Triangle
33. Ways to Reach Position After K Steps
int numberOfWays(int startPos, int endPos, int k) {
int diff = abs(endPos - startPos);
if (diff > k || (k - diff) % 2 != 0) return 0;
// FIX: System of equations: R + L = k, R - L = diff
// CALCULATE: R = (k + diff) / 2. Ans is k Choose R.
long long res = 1, mod = 1e9+7;
int r = (k + diff) / 2;
// Calculate nCr % mod ...
return res;
}