Skip to content

The Fixing Mindset

You're staring at a problem. Two arrays, some constraint about pairs, maybe a target sum. Your brain does what it always does — scans for a pattern. "Constraints say N is 10^5... so I probably need O(N log N)... maybe binary search? Or sorting + two pointers?"

Stop. That's not thinking. That's pattern matching.

It works sometimes. It fails the moment you hit something you haven't seen before. And in an interview or a contest, that's exactly when it fails — on the problem that actually matters.

This page is about a different way to approach problems. It's called parameter fixing, and it's the single most useful thinking tool in competitive programming. The funny thing is, you already use it. You just don't know you're using it.


The Core Idea

Here it is in one sentence:

Pick one thing. Hold it still. See what the rest of the problem has to be.

That's it. Every time you write for (int i = 0; i < n; i++), you're fixing i. You're saying, "Let me assume I'm standing at index i. Now, given that I'm here, what do I need?" The loop just moves you to the next fixed position.

Kadane's algorithm? You're fixing the endpoint of the subarray and asking, "What's the best subarray that ends right here?"

Prefix sums? You're fixing a range [l, r] and expressing it as prefix[r] - prefix[l-1] — you turned a range question into a subtraction between two fixed points.

You've been doing this your whole life. The goal of this page is to make you conscious of it, so you can reach for it deliberately when you're stuck.


The Problem With Pattern Matching

Let's be honest about what most people do when they see a competitive programming problem:

  • Constraints say \(N \le 1000\)? Try \(O(N^2)\). Maybe DP.
  • Constraints say \(N \le 10^5\)? Must be \(O(N \log N)\). Sort something. Binary search something.
  • Constraints say \(N \le 10^6\)? Probably \(O(N)\). Greedy? Prefix sums?

This isn't wrong — it gives you a rough target complexity. But it tells you nothing about how to get there. You end up guessing algorithms instead of deriving them. And when the problem doesn't fit a template you've memorized, you're stuck.

The fixing mindset replaces guessing with a question: "What can I fix to make this simpler?"


Two Sum: A Walkthrough

Let's make this concrete with the most classic problem in existence.

Problem: Given an array and a target, find two numbers that add up to the target.

The Brute Force Way

Check every pair. For each i, try every j > i. If A[i] + A[j] == target, you're done.

for i in range(n):
    for j in range(i+1, n):
        if A[i] + A[j] == target:
            return (i, j)

This is \(O(N^2)\). It works, but it's slow. And here's the thing — most people jump straight from this to "use a hash map" because they've memorized the solution. Let's instead derive it using the fixing mindset.

Applying the Fix

You're iterating through the array. At index i, you look at A[i]. Now fix it. Hold A[i] still and ask:

"If A[i] is one of the two numbers, what does the other number HAVE to be?"

Simple algebra:

\[A[i] + A[j] = \text{target} \implies A[j] = \text{target} - A[i]\]

So the other number isn't a mystery anymore. It's exactly target - A[i]. You don't need to search for it with a second loop. You just need to check if you've seen it before.

And what's the fastest way to check if you've seen a value? A hash map.

That's the whole insight. The hash map isn't a trick you memorize. It's a natural consequence of fixing one variable and calculating what the other one must be.

The Code

vector<int> twoSum(vector<int>& nums, int target) {
    unordered_map<int, int> seen;
    for (int i = 0; i < nums.size(); ++i) {
        // FIX: the current element nums[i]
        int needed = target - nums[i];
        // CALCULATE: does the required partner exist?
        if (seen.count(needed))
            return {seen[needed], i};
        seen[nums[i]] = i;
    }
    return {};
}

One pass. \(O(N)\) time. And you didn't need to memorize anything — you derived it by asking one question: "If I fix this, what must the other thing be?"


Fixing Isn't Just for Pairs

The Two Sum example fixes a value and deduces another value. But that's just one flavor. The same principle applies to wildly different settings:

What you fix What you deduce Example
A value A[i] Its required partner Two Sum, Count Bad Pairs
A prefix sum's remainder Whether a valid subarray exists Subarray Sum Divisible by K
An edge in a tree How many paths cross it Tree path counting
An element as "the minimum" How many subarrays it dominates Sum of Subarray Minimums
A connected component How many nodes are outside it Count Unreachable Pairs

Don't worry about the tree and graph rows yet — those come in later pages. The point is that the thinking pattern is identical every time. Fix something. Deduce the rest.


The Four Questions

When you're stuck on a problem, run through these:

1. What can I fix? Look at the variables in the problem. An index, an element, an edge, a component, a remainder. Which one, if you held it still, would simplify things?

2. What does fixing it force? Once you fix one thing, what must the remaining variable(s) be? Can you write an equation? Can you express the "other thing" in terms of what you fixed?

3. What remembers what I've seen? You've fixed something, and now you need to check or count something. What data structure lets you do that fast? A hash map? A sorted set? A prefix array?

4. What's the complexity now? Fixing typically turns an \(O(N^2)\) "check all pairs" into \(O(N)\) or \(O(N \log N)\) "for each element, do a fast lookup."

These four questions are your starting point for every problem from here on out. Not "what algorithm should I use?" but "what should I fix?"


A Second Example: Count Good Pairs

Problem: Given an array, count pairs \((i, j)\) where \(i < j\) and \(\text{nums}[i] == \text{nums}[j]\).

The brute force checks all pairs — \(O(N^2)\).

Let's fix. As you iterate, fix the current element nums[i]. Ask: "How many elements BEFORE me have the same value?" Each of those is a valid pair.

A hash map counting frequencies gives you the answer instantly.

int numIdenticalPairs(vector<int>& nums) {
    int cnt[101] = {}, ans = 0;
    for (int x : nums) {
        // FIX: the current element x
        // CALCULATE: how many times have I seen x before?
        ans += cnt[x]++;
    }
    return ans;
}

\(O(N)\). Same thinking pattern. Fix, deduce, look up.


A Third Example: Count Bad Pairs

Problem: Count pairs where \(j - i \ne \text{nums}[j] - \text{nums}[i]\).

This one looks messy. But rearrange the equation:

\[j - i \ne \text{nums}[j] - \text{nums}[i]$$ $$\implies \text{nums}[i] - i \ne \text{nums}[j] - j\]

Now define a new value: \(\text{val}[i] = \text{nums}[i] - i\). The condition becomes \(\text{val}[i] \ne \text{val}[j]\) — pairs where the transformed values are different.

Counting "different" pairs directly is annoying. But counting "same" pairs is easy (it's the Good Pairs problem above). So:

\[\text{Bad Pairs} = \text{Total Pairs} - \text{Good Pairs} = \frac{N(N-1)}{2} - \text{Good Pairs}\]
long long countBadPairs(vector<int>& nums) {
    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: how many previous indices have the same transformed value?
        if (m.count(val)) good += m[val];
        m[val]++;
    }
    return (n * (n - 1) / 2) - good;
}

Notice what happened here. Two fixing tricks stacked:

  1. Fix the equation — rearrange to group i-stuff and j-stuff separately.
  2. Fix the complement — count the easy thing (good pairs) and subtract from total.

This is where the mindset starts to compound. The more you practice, the more naturally these rearrangements come to you.


What's Next

This page gave you the philosophy. The next page — Fix the Equation — goes deep on the most common application: using hash maps and prefix sums to solve array problems. You'll work through 10 practice problems, starting easy and getting progressively harder.

After that, we'll apply fixing to trees, then to graphs. But the thinking stays the same. Always the same four questions. Always the same core move: pick one thing, hold it still, see what has to be true.