Skip to content
← All posts

Get Equal Substrings Within Budget: Sliding Window Cost Control

5 min read
leetcodeproblemmediumstringsbinary-searchsliding-window

LeetCode 1208 Get Equal Substrings Within Budget gives you two strings s and t of the same length, plus an integer maxCost. You can change s[i] to t[i] at a cost of |s[i] - t[i]| (the absolute difference of their ASCII values). Your goal is to find the maximum length of a substring of s that you can convert to the corresponding substring of t without the total cost exceeding maxCost.

For example, given s = "abcd", t = "bcdf", and maxCost = 3, the per-position costs are [1, 1, 1, 2]. The longest substring you can convert is "abc" to "bcd" at cost 1+1+1 = 3. The answer is 3.

If you have worked through Longest Substring Without Repeating Characters or Longest Repeating Character Replacement, you will recognize the sliding window structure here. The twist is that the window constraint is a running cost sum rather than a character frequency condition.

sa0b1c2d3tbcdfcost = |s[i] - t[i]|1112leftrightcost = 3 3 (maxCost)
Strings s and t aligned with per-position costs below. The window [0..2] has total cost 1+1+1 = 3, which fits within maxCost = 3. This is the longest valid window.

Why this problem matters

This problem teaches you to apply the sliding window pattern to a numeric constraint. Many real-world problems boil down to "find the longest contiguous range where some running total stays within a limit." Network packet windows, time-series analysis, and resource budgeting all follow this shape.

Once you see that the problem reduces to "maximum-length subarray with sum <= maxCost," the sliding window template drops in directly. That reduction step, transforming a string problem into a numeric one, is the key skill this problem drills.

The key insight

The first step is to realize you do not need to think about characters at all. Compute a cost array where cost[i] = |s[i] - t[i]|. Now the problem becomes: find the longest contiguous subarray of cost whose sum is at most maxCost.

This is a classic sliding window setup. You maintain a window [left, right] and a running currentCost that tracks the sum of costs inside the window. Expand right to grow the window. When currentCost exceeds maxCost, shrink from the left by subtracting cost[left] and advancing left.

Because both pointers only move forward, the total work is O(n). You never backtrack, you never recompute sums from scratch. The running total is maintained incrementally: add when expanding, subtract when shrinking.

The solution

def equalSubstring(s, t, maxCost):
    n = len(s)
    left = 0
    current_cost = 0
    best = 0

    for right in range(n):
        current_cost += abs(ord(s[right]) - ord(t[right]))

        while current_cost > maxCost:
            current_cost -= abs(ord(s[left]) - ord(t[left]))
            left += 1

        best = max(best, right - left + 1)

    return best

Here is what each part does:

  1. current_cost tracks the sum of |s[i] - t[i]| for every index in the current window.
  2. The for loop advances right one position at a time, adding the new position's cost to current_cost.
  3. The while loop fires when the budget is exceeded. It subtracts the cost at left and moves left forward until the window is back within budget.
  4. After each expansion (and possible shrink), we update best with the current window length.

You do not need to precompute the cost array into a separate list. Computing abs(ord(s[right]) - ord(t[right])) on the fly keeps space at O(1) and avoids an extra pass.

Visual walkthrough

Step 1: right = 0. Add cost[0] = 1. Window = [0..0], total cost = 1.

a0b1c2d3costs1112leftrightcost = 1 3 (maxCost)

best = 1. Cost 1 fits within maxCost = 3.

Step 2: right = 1. Add cost[1] = 1. Window = [0..1], total cost = 2.

a0b1c2d3costs1112leftrightcost = 2 3 (maxCost)

best = 2. Cost 2 fits within maxCost = 3.

Step 3: right = 2. Add cost[2] = 1. Window = [0..2], total cost = 3.

a0b1c2d3costs1112leftrightcost = 3 3 (maxCost)

best = 3. Cost exactly equals maxCost. This is the optimal window.

Step 4: right = 3. Add cost[3] = 2. Window = [0..3], total cost = 5. Over budget!

a0b1c2d3costs1112leftrightcost = 5 > 3 (maxCost)

Cost 5 exceeds maxCost = 3. We need to shrink from the left.

Step 5: Shrink. Remove cost[0] = 1. left moves to 1. Total cost = 4. Still over!

a0b1c2d3costs1112leftrightcost = 4 > 3 (maxCost)

Cost 4 still exceeds maxCost = 3. Keep shrinking.

Step 6: Shrink again. Remove cost[1] = 1. left moves to 2. Total cost = 3. Valid!

a0b1c2d3costs1112leftrightcost = 3 3 (maxCost)

Cost 3 fits within maxCost = 3. Window [2..3] has length 2. best stays at 3.

Step 7: right has reached the end. The algorithm terminates.

a0b1c2d3costs1112leftrightcost = 3 3 (maxCost)

The longest valid window was [0..2] with length 3 and total cost 3. Answer: 3.

Notice how the window grows freely when costs are small, then shrinks rapidly when it hits the expensive position at index 3 (cost = 2). The key observation: after shrinking, the algorithm does not restart from the beginning. It picks up right where it left off, which is why the total work stays linear.

Complexity analysis

ApproachTimeSpace
Brute forceO(n^2)O(n)
Sliding windowO(n)O(1)

Time: O(n). The right pointer visits each index once. The left pointer also visits each index at most once. Every element is added to and subtracted from current_cost at most once, giving at most 2n operations total.

Space: O(1). We only store left, current_cost, and best. No auxiliary arrays or data structures are needed since we compute costs on the fly.

Edge cases

  • All costs are zero: every character already matches. The answer is n because the entire string is free to "convert."
  • maxCost is zero: only positions where s[i] == t[i] (cost zero) can be included. The answer is the longest run of matching characters.
  • Single character: the answer is 1 if |s[0] - t[0]| is at most maxCost, otherwise 0.
  • maxCost is very large: the budget never runs out. The answer is n because the entire string fits.
  • Every position has high cost: if every individual cost exceeds maxCost, the answer is 0 because no single character can be converted.
  • Strings of length 1: a minimal edge case that tests the boundary of the loop logic.

The building blocks

1. Transform to a cost array

The first building block is recognizing that character-level differences can be reduced to a numeric array. This "reduce the problem" step appears in many string problems.

cost_i = abs(ord(s[i]) - ord(t[i]))

Once you have a numeric array, you can apply standard array techniques (prefix sums, sliding windows, binary search) without worrying about character logic.

2. Maximum-length subarray with bounded sum

The second building block is the sliding window over a non-negative array with a sum constraint. The window expands greedily and shrinks only when the constraint is violated. This is the same skeleton used in minimum size subarray sum, subarray product less than k, and many other problems.

left = 0
running_sum = 0
for right in range(n):
    running_sum += arr[right]
    while running_sum > limit:
        running_sum -= arr[left]
        left += 1
    best = max(best, right - left + 1)

The non-negative property of the cost values is critical here. It guarantees that shrinking the window always reduces the sum, so the while loop always terminates. If values could be negative, this approach would break and you would need a different technique like a deque or prefix sum with binary search.

From understanding to recall

Reading through this solution once gives you the idea. But when you sit down in an interview next month, you need to reproduce the transform-then-slide approach from memory. Spaced repetition bridges that gap. By revisiting the cost-array reduction and the bounded-sum window template at increasing intervals, you build the muscle memory to assemble them on the fly, even under pressure.

Related posts