AlgoAtlas

Sliding Window

Phase 1 18 solutions

Sliding Window

mindmap
  root((Sliding Window))
    Fixed Window
      Permutation In String LC567
      Contains Duplicate II LC219
      Maximum Points from Cards LC1423
    Variable Window Shrinkable
      Minimum Size Subarray Sum LC209
      Minimum Window Substring LC76
      Longest Substring Without Repeating Characters LC3
      Max Consecutive Ones III LC1004
      Fruit Into Baskets LC904
      Longest Substring K Distinct LC340
    Frequency Tracking
      Longest Repeating Character Replacement LC424
      Permutation In String LC567
      Number of Substrings All Three LC1358
    Monotonic Deque
      Sliding Window Maximum LC239
    atMost Trick (Exact Count)
      Binary Subarrays With Sum LC930
      Count Nice Subarrays LC1248
      Subarrays K Different Integers LC992
    Subsequence Window
      Minimum Window Subsequence LC727
    Other
      Best Time to Buy And Sell Stock LC121
      Find K Closest Elements LC658

Problem List

# Problem Difficulty Technique
1 Contains Duplicate II (LC 219) Easy Hash Set Window
2 Best Time to Buy And Sell Stock (LC 121) Easy Min Tracking
3 Longest Substring Without Repeating Characters (LC 3) Medium Hash Set/Map
4 Longest Repeating Character Replacement (LC 424) Medium Max Freq Tracking
5 Permutation In String (LC 567) Medium Frequency Array
6 Minimum Size Subarray Sum (LC 209) Medium Shrinkable Window
7 Find K Closest Elements (LC 658) Medium Binary Search + Window
8 Minimum Window Substring (LC 76) Hard Shrinkable Window + Counter
9 Sliding Window Maximum (LC 239) Hard Monotonic Deque
10 Max Consecutive Ones III (LC 1004) Medium Shrinkable Window
11 Fruit Into Baskets (LC 904) Medium Variable Window + HashMap
12 Longest Substring with At Most K Distinct (LC 340) Medium Variable Window + HashMap
13 Maximum Points from Cards (LC 1423) Medium Fixed Window (complement)
14 Binary Subarrays with Sum (LC 930) Medium atMost trick
15 Count Nice Subarrays (LC 1248) Medium atMost trick
16 Subarrays with K Different Integers (LC 992) Hard atMost trick
17 Number of Substrings Containing All Three Characters (LC 1358) Medium Frequency Tracking
18 Minimum Window Subsequence (LC 727) Hard Two-pointer subsequence

Mermaid Diagrams

1. When to Use Sliding Window

flowchart TD
    A["Problem involves contiguous subarray/substring?"] -->|No| B[Not sliding window]
    A -->|Yes| C[Is window size fixed?]
    C -->|Yes| D["Fixed Window<br/>LC 567, LC 219"]
    C -->|No| E["Need to find min/max window?"]
    E -->|Min window meeting condition| F["Shrinkable Window<br/>LC 76, LC 209"]
    E -->|Max window meeting condition| G["Expandable Window<br/>LC 3, LC 424"]
    E -->|Max in each window| H["Monotonic Deque<br/>LC 239"]

2. Fixed vs Variable Window Decision Tree

flowchart TD
    A["Subarray/Substring Problem"] --> B{Window size given?}
    B -->|Yes - size k| C[Fixed Window]
    B -->|No| D{Optimize for?}
    C --> C1["Slide: remove left, add right<br/>Maintain window invariant"]
    D -->|Minimum length| E["Variable - Shrink when valid<br/>LC 76, LC 209"]
    D -->|Maximum length| F["Variable - Shrink when invalid<br/>LC 3, LC 424"]
    D -->|Max element per window| G["Monotonic Deque<br/>LC 239"]

3. Expand/Shrink Window Pattern Flow

flowchart LR
    A(["Start<br/>l=0, r=0"]) --> B[Expand: add arr-r to window]
    B --> C{Window valid?}
    C -->|Yes| D[Update answer]
    D --> E[r++]
    E --> B
    C -->|No - shrink needed| F["Remove arr-l from window<br/>l++"]
    F --> C
    E --> G{r == n?}
    G -->|Yes| H([Return answer])
    G -->|No| B

4. Monotonic Deque Pattern (LC 239)

flowchart TD
    A[New element arr-i] --> B{"Deque not empty AND<br/>deque.back <= arr-i?"}
    B -->|Yes - pop smaller| C[deque.pop_back]
    C --> B
    B -->|No| D[deque.push_back index i]
    D --> E{"deque.front out of window?<br/>front < i - k + 1"}
    E -->|Yes| F[deque.pop_front]
    F --> E
    E -->|No| G[Window max = arr-deque.front]
    G --> H[Append to result if i >= k-1]

Complexity Summary

Problem Time Space Pattern
contains_duplicate_ii O(n) O(k) Hash set fixed window
best_time_to_buy_and_sell_stock O(n) O(1) Min tracking
longest_substring_without_repeating_characters O(n) O(min(n,m)) Shrink on duplicate
longest_repeating_character_replacement O(n) O(1) Max freq tracking
permutation_in_string O(n) O(1) Fixed window freq match
minimum_size_subarray_sum O(n) O(1) Shrink when sum ≥ target
find_k_closest_elements O(log n) O(1) Binary search left boundary
minimum_window_substring O(n) O(m+n) Two counters: need/have
sliding_window_maximum O(n) O(k) Monotonic decreasing deque
max_consecutive_ones_iii O(n) O(1) Shrinkable window (k zeros)
fruit_into_baskets O(n) O(1) Variable window + hashmap
binary_subarrays_with_sum O(n) O(n) atMost trick
count_number_of_nice_subarrays O(n) O(n) atMost trick
number_of_substrings_containing_all_three_characters O(n) O(1) Frequency tracking
maximum_points_you_can_obtain_from_cards O(k) O(1) Fixed window (complement)
longest_substring_with_at_most_k_distinct_characters O(n) O(k) Variable window + hashmap
subarrays_with_k_different_integers O(n) O(n) atMost trick
minimum_window_subsequence O(m·n) O(1) Two-pointer subsequence

Study Order

flowchart LR
    subgraph S1["Stage 1: Easy"]
        sw1["best_time_stock(121) → contains_duplicate_ii(219)"]
        sw2["longest_substr_no_repeat(3)"]
    end

    subgraph S2["Stage 2: Variable Window"]
        sw3["max_consecutive_ones_iii(1004) → fruit_into_baskets(904)"]
        sw4["longest_substr_k_distinct(340) → minimum_size_subarray_sum(209)"]
        sw5["longest_repeating_replacement(424)"]
    end

    subgraph S3["Stage 3: Fixed Window + atMost Trick"]
        sw6["permutation_in_string(567) → maximum_points_from_cards(1423)"]
        sw7["find_k_closest_elements(658) → binary_subarrays_with_sum(930)"]
        sw8["count_nice_subarrays(1248) → subarrays_k_different(992)"]
        sw9["number_of_substrings_all_three(1358)"]
    end

    subgraph S4["Stage 4: Hard"]
        sw10["minimum_window_substring(76) → sliding_window_maximum(239)"]
        sw11["minimum_window_subsequence(727)"]
    end

    S1 --> S2 --> S3 --> S4

Recommended order: Easy fixed → Easy variable → Medium fixed → Medium variable → Hard

Sliding Window — Pattern Notes

Core Intuition

Sliding window converts O(n²) brute force (check every subarray) into O(n) by maintaining a window that expands and contracts. The key question: when do I shrink the window?

  • Fixed window: Window size is given. Slide by adding right element and removing left element.
  • Variable window (find max): Expand right freely, shrink left when constraint is violated.
  • Variable window (find min): Expand right until constraint is met, then shrink left as much as possible.
  • Frequency tracking: Use a hashmap/array to track character counts inside the window.
  • Monotonic deque: For window maximum/minimum — maintain a deque of useful candidates.

Decision Flowchart

flowchart TD
    A[Sliding Window Problem] --> B{Window size fixed?}
    B -- Yes --> C["Fixed window<br/>Add right, remove left at each step"]
    B -- No --> D{Minimize or Maximize?}
    D -- Maximize --> E["Expand right freely<br/>Shrink left when invalid<br/>Track max valid window"]
    D -- Minimize --> F["Expand until valid<br/>Shrink left while still valid<br/>Track min window size"]
    E --> G{"Need element ordering<br/>in window?"}
    G -- Yes --> H[Monotonic Deque]
    G -- No --> I["HashMap / frequency array"]
    F --> I

Per-Problem Notes

1. Contains Duplicate II — LC 219 | Easy

  • Key insight: Fixed window of size k. Use a HashSet. If adding element already in set → True. Remove nums[i-k] when window exceeds k.
  • Tricky: Window size is k, so remove element at i-k (not i-k-1).
  • Common mistakes: Off-by-one in window removal. Using hashmap instead of hashset (overkill).
  • Time: O(n) | Space: O(k)

2. Best Time to Buy/Sell Stock — LC 121 | Easy

  • Key insight: Track running minimum (buy price). For each day, profit = price - min_so_far. Track max profit.
  • Tricky: This is technically a sliding window where left = min price day, right = current day.
  • Common mistakes: Updating max profit before updating min price. Allowing sell before buy.
  • Time: O(n) | Space: O(1)

3. Longest Substring Without Repeating Characters — LC 3 | Medium

  • Key insight: Variable window. HashMap stores last seen index of each char. When duplicate found, move left to max(left, last_seen[char] + 1).
  • Tricky: Move left to max(left, ...) — don't move left backward if the duplicate is outside current window.
  • Common mistakes: Moving left to last_seen[char] + 1 without the max → left can move backward.
  • Time: O(n) | Space: O(min(n, 26))

4. Longest Repeating Character Replacement — LC 424 | Medium

  • Key insight: Window is valid if window_size - max_freq <= k. Expand right, track max frequency of any char. Shrink left when invalid.
  • Tricky: max_freq never decreases (only update when new char beats it). This is a subtle optimization — we only care about windows larger than the best seen.
  • Common mistakes: Recalculating max_freq when shrinking (O(26) per step, still O(n) total but unnecessary). Not understanding why max_freq doesn't need to decrease.
  • Time: O(n) | Space: O(26) = O(1)

5. Permutation in String — LC 567 | Medium

  • Key insight: Fixed window of size len(s1). Compare frequency arrays of window and s1. Slide window across s2.
  • Tricky: Compare 26-element arrays at each step. Or track a matches counter (number of chars with equal frequency).
  • Common mistakes: Sorting window at each step (O(n·k log k)). Not using fixed window size.
  • Time: O(n) | Space: O(26) = O(1)

6. Minimum Size Subarray Sum — LC 209 | Medium

  • Key insight: Variable window (minimize). Expand right, shrink left while sum >= target. Track min length.
  • Tricky: Shrink as much as possible while constraint is still satisfied.
  • Common mistakes: Shrinking only once per right expansion. Not handling case where no valid subarray exists (return 0).
  • Time: O(n) | Space: O(1)

7. K Closest Elements — LC 658 | Medium

  • Key insight: Binary search for the left boundary of the window of size k. Compare x - arr[mid] vs arr[mid+k] - x to decide which side to keep.
  • Tricky: Binary search on the left index, not on the value. Condition: x - arr[mid] > arr[mid+k] - x → move left right.
  • Common mistakes: Binary searching on value instead of index. Wrong comparison direction.
  • Time: O(log(n-k) + k) | Space: O(1)

8. Minimum Window Substring — LC 76 | Hard

  • Key insight: Variable window (minimize). Expand right until all chars of t are covered. Then shrink left while still valid. Track min window.
  • Tricky: Use have and need counters. have increments only when a char's count in window matches its count in t.
  • Common mistakes: Incrementing have for every occurrence instead of only when count matches. Not handling chars not in t.
  • Time: O(n + m) | Space: O(n + m)

9. Sliding Window Maximum — LC 239 | Hard

  • Key insight: Monotonic decreasing deque stores indices. Front of deque is always the max of current window. Remove indices outside window from front. Remove smaller elements from back before adding new element.
  • Tricky: Store indices (not values) in deque to check if front is outside window. Remove from back while nums[deque[-1]] <= nums[i].
  • Common mistakes: Storing values instead of indices (can't check window boundary). Not removing out-of-window indices from front.
  • Time: O(n) | Space: O(k)

Edge Cases to Watch For

  • Empty string/array: Return 0 or "" immediately.
  • Window larger than array: k > n — return entire array or handle gracefully.
  • All same characters: Longest repeating replacement — entire string is valid if k ≥ 0.
  • Target not achievable: Min window substring — return "" if t chars not all in s. Min subarray sum — return 0 if no valid subarray.
  • Single character strings: Permutation in string — trivially check if s1 char is in s2.
  • Negative numbers: Sliding window generally doesn't work with negatives for sum problems (use prefix sum + hashmap instead).
  • Duplicate characters in t: Min window substring — must match counts, not just presence.
  • k = 0: Contains Dup II — only exact same index duplicates count (impossible), return False.
# Problem Difficulty Technique
1 Contains Duplicate II (LC 219) Easy Hash Set Window
2 Best Time to Buy And Sell Stock (LC 121) Easy Min Tracking
3 Longest Substring Without Repeating Characters (LC 3) Medium Hash Set/Map
4 Longest Repeating Character Replacement (LC 424) Medium Max Freq Tracking
5 Permutation In String (LC 567) Medium Frequency Array
6 Minimum Size Subarray Sum (LC 209) Medium Shrinkable Window
7 Find K Closest Elements (LC 658) Medium Binary Search + Window
8 Minimum Window Substring (LC 76) Hard Shrinkable Window + Counter
9 Sliding Window Maximum (LC 239) Hard Monotonic Deque
10 Max Consecutive Ones III (LC 1004) Medium Shrinkable Window
11 Fruit Into Baskets (LC 904) Medium Variable Window + HashMap
12 Longest Substring with At Most K Distinct (LC 340) Medium Variable Window + HashMap
13 Maximum Points from Cards (LC 1423) Medium Fixed Window (complement)
14 Binary Subarrays with Sum (LC 930) Medium atMost trick
15 Count Nice Subarrays (LC 1248) Medium atMost trick
16 Subarrays with K Different Integers (LC 992) Hard atMost trick
17 Number of Substrings Containing All Three Characters (LC 1358) Medium Frequency Tracking
18 Minimum Window Subsequence (LC 727) Hard Two-pointer subsequence
Best Time To Buy And Sell Stock ▼ expand
from typing import List


class Solution:
    """
    ## 121. Best Time to Buy and Sell Stock

    You are given an array `prices` where `prices[i]` is the price of a given stock on
    the `i`-th day. You want to maximize your profit by choosing a single day to buy one
    stock and choosing a different day in the future to sell that stock.

    Return the maximum profit you can achieve from this transaction. If you cannot
    achieve any profit, return `0`.

    ### Examples

    ```text
    Input:  prices = [7, 1, 5, 3, 6, 4]
    Output: 5
    Explanation: Buy on day 2 (price=1), sell on day 5 (price=6), profit = 6-1 = 5.

    Input:  prices = [7, 6, 4, 3, 1]
    Output: 0
    Explanation: No profitable transaction possible.
    ```

    ### Constraints

    - `1 <= prices.length <= 10^5`
    - `0 <= prices[i] <= 10^4`
    """

    def maxProfit_brute(self, prices: List[int]) -> int:
        # Try every buy-sell pair — O(n^2)
        # O(n^2) time, O(1) space
        max_p = 0
        for i in range(len(prices)):
            for j in range(i + 1, len(prices)):
                max_p = max(max_p, prices[j] - prices[i])
        return max_p

    def maxProfit(self, prices: List[int]) -> int:
        # Track the minimum price seen so far as the best buy day
        # At each price, compute profit if we sold today; update best
        # O(n) time, O(1) space
        min_price, max_p = float("inf"), 0
        for price in prices:
            min_price = min(min_price, price)
            max_p = max(max_p, price - min_price)
        return max_p


if __name__ == "__main__":
    sol = Solution()
    assert sol.maxProfit([7, 1, 5, 3, 6, 4]) == 5
    assert sol.maxProfit([7, 6, 4, 3, 1]) == 0
    assert sol.maxProfit([1, 2]) == 1
    assert sol.maxProfit_brute([7, 1, 5, 3, 6, 4]) == 5
    assert sol.maxProfit_brute([7, 6, 4, 3, 1]) == 0
    print("All tests passed.")
Binary Subarrays With Sum ▼ expand
"""
LC 930 - Binary Subarrays With Sum

Given a binary array nums and an integer goal, return the number of
non-empty subarrays with a sum equal to goal.

Examples:
```text
Input: nums = [1,0,1,0,1], goal = 2
Output: 4

Input: nums = [0,0,0,0,0], goal = 0
Output: 15
```

Constraints:
- 1 <= nums.length <= 3 * 10^4
- nums[i] is either 0 or 1
- 0 <= goal <= nums.length
"""

from typing import List
from collections import defaultdict


class Solution:
    def numSubarraysWithSum(self, nums: List[int], goal: int) -> int:
        # Approach 1: prefix sum + hash map
        # count[prefix] = how many times this prefix sum has appeared
        count = defaultdict(int)
        count[0] = 1
        prefix = 0
        result = 0

        for num in nums:
            prefix += num
            # subarrays ending here with sum == goal
            result += count[prefix - goal]
            count[prefix] += 1

        return result

    def numSubarraysWithSum_atmost(self, nums: List[int], goal: int) -> int:
        # Approach 2: atMost(goal) - atMost(goal-1)
        # The atMost trick converts exact-count to two atMost calls
        def atMost(g: int) -> int:
            if g < 0:
                return 0
            left = curr_sum = res = 0
            for right in range(len(nums)):
                curr_sum += nums[right]
                # shrink window until sum <= g
                while curr_sum > g:
                    curr_sum -= nums[left]
                    left += 1
                # all subarrays ending at right with sum <= g
                res += right - left + 1
            return res

        return atMost(goal) - atMost(goal - 1)


if __name__ == '__main__':
    s = Solution()
    assert s.numSubarraysWithSum([1,0,1,0,1], 2) == 4
    assert s.numSubarraysWithSum([0,0,0,0,0], 0) == 15
    assert s.numSubarraysWithSum_atmost([1,0,1,0,1], 2) == 4
    assert s.numSubarraysWithSum_atmost([0,0,0,0,0], 0) == 15
    print("All tests passed.")
Contains Duplicate Ii ▼ expand
from typing import List


class Solution:
    """
    ## 219. Contains Duplicate II

    Given an integer array `nums` and an integer `k`, return `true` if there exist two
    distinct indices `i` and `j` in the array such that `nums[i] == nums[j]` and
    `abs(i - j) <= k`.

    ### Examples

    ```text
    Input:  nums = [1, 2, 3, 1], k = 3
    Output: true

    Input:  nums = [1, 0, 1, 1], k = 1
    Output: true

    Input:  nums = [1, 2, 3, 1, 2, 3], k = 2
    Output: false
    ```

    ### Constraints

    - `1 <= nums.length <= 10^5`
    - `-10^9 <= nums[i] <= 10^9`
    - `0 <= k <= 10^5`
    """

    def containsNearbyDuplicate_brute(self, nums: List[int], k: int) -> bool:
        # For each element, check the next k elements for a duplicate — O(n*k)
        # O(n * k) time, O(1) space
        for i in range(len(nums)):
            for j in range(i + 1, min(i + k + 1, len(nums))):
                if nums[i] == nums[j]:
                    return True
        return False

    def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
        # Maintain a sliding set of size k; if new element already in set, found duplicate
        # Remove the element that just fell out of the window (nums[i-k])
        # O(n) time, O(k) space
        window = set()
        for i, num in enumerate(nums):
            if num in window:
                return True
            window.add(num)
            if len(window) > k:
                window.remove(nums[i - k])
        return False


if __name__ == "__main__":
    sol = Solution()
    assert sol.containsNearbyDuplicate([1, 2, 3, 1], 3) == True
    assert sol.containsNearbyDuplicate([1, 0, 1, 1], 1) == True
    assert sol.containsNearbyDuplicate([1, 2, 3, 1, 2, 3], 2) == False
    assert sol.containsNearbyDuplicate_brute([1, 2, 3, 1], 3) == True
    assert sol.containsNearbyDuplicate_brute([1, 2, 3, 1, 2, 3], 2) == False
    print("All tests passed.")
Count Number Of Nice Subarrays ▼ expand
"""
LC 1248 - Count Number of Nice Subarrays

Given an array of integers nums and an integer k, return the number of
nice sub-arrays. A subarray is nice if there are k odd numbers in it.

Examples:
```text
Input: nums = [1,1,2,1,1], k = 3
Output: 2

Input: nums = [2,4,6], k = 1
Output: 0

Input: nums = [2,2,2,1,2,2,1,2,2,2], k = 2
Output: 16
```

Constraints:
- 1 <= nums.length <= 5 * 10^4
- 1 <= nums[i] <= 10^5
- 1 <= k <= nums.length
"""

from typing import List
from collections import defaultdict


class Solution:
    def numberOfSubarrays(self, nums: List[int], k: int) -> int:
        # Approach 1: prefix sum
        # Treat odd as 1, even as 0 — same as binary subarrays with sum k
        count = defaultdict(int)
        count[0] = 1
        prefix = 0
        result = 0

        for num in nums:
            prefix += num % 2          # 1 if odd, 0 if even
            result += count[prefix - k]
            count[prefix] += 1

        return result

    def numberOfSubarrays_atmost(self, nums: List[int], k: int) -> int:
        # Approach 2: atMost(k) - atMost(k-1)
        # exactly(k) = atMost(k) - atMost(k-1)
        def atMost(limit: int) -> int:
            left = odds = res = 0
            for right in range(len(nums)):
                odds += nums[right] % 2
                while odds > limit:
                    odds -= nums[left] % 2
                    left += 1
                res += right - left + 1
            return res

        return atMost(k) - atMost(k - 1)


if __name__ == '__main__':
    s = Solution()
    assert s.numberOfSubarrays([1,1,2,1,1], 3) == 2
    assert s.numberOfSubarrays([2,4,6], 1) == 0
    assert s.numberOfSubarrays([2,2,2,1,2,2,1,2,2,2], 2) == 16
    assert s.numberOfSubarrays_atmost([1,1,2,1,1], 3) == 2
    assert s.numberOfSubarrays_atmost([2,2,2,1,2,2,1,2,2,2], 2) == 16
    print("All tests passed.")
Find K Closest Elements ▼ expand
from typing import List
import bisect


class Solution:
    """
    ## 658. Find K Closest Elements

    Given a sorted integer array `arr`, two integers `k` and `x`, return the `k`
    closest integers to `x` in the array. The result should also be sorted in ascending
    order.

    An integer `a` is closer to `x` than an integer `b` if:
    - `|a - x| < |b - x|`, or
    - `|a - x| == |b - x|` and `a < b`

    ### Examples

    ```text
    Input:  arr = [1, 2, 3, 4, 5], k = 4, x = 3
    Output: [1, 2, 3, 4]

    Input:  arr = [1, 2, 3, 4, 5], k = 4, x = -1
    Output: [1, 2, 3, 4]
    ```

    ### Constraints

    - `1 <= k <= arr.length <= 10^4`
    - `arr` is sorted in ascending order.
    - `-10^4 <= arr[i], x <= 10^4`
    """

    def findClosestElements_sort(self, arr: List[int], k: int, x: int) -> List[int]:
        # Sort by distance to x, take first k, then re-sort for ascending order
        # O(n log n) time, O(n) space
        return sorted(sorted(arr, key=lambda a: (abs(a - x), a))[:k])

    def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
        # Binary search for the left boundary of the k-element window
        # Compare distance of left edge vs right edge: x-arr[mid] vs arr[mid+k]-x
        # Move window right if left edge is farther from x than right edge
        # O(log n + k) time, O(1) space
        lo, hi = 0, len(arr) - k
        while lo < hi:
            mid = (lo + hi) // 2
            if x - arr[mid] > arr[mid + k] - x:
                lo = mid + 1
            else:
                hi = mid
        return arr[lo:lo + k]


if __name__ == "__main__":
    sol = Solution()
    assert sol.findClosestElements([1, 2, 3, 4, 5], 4, 3) == [1, 2, 3, 4]
    assert sol.findClosestElements([1, 2, 3, 4, 5], 4, -1) == [1, 2, 3, 4]
    assert sol.findClosestElements([1, 2, 3, 4, 5], 4, 10) == [2, 3, 4, 5]
    assert sol.findClosestElements_sort([1, 2, 3, 4, 5], 4, 3) == [1, 2, 3, 4]
    assert sol.findClosestElements_sort([1, 2, 3, 4, 5], 4, -1) == [1, 2, 3, 4]
    print("All tests passed.")
Fruit Into Baskets ▼ expand
"""
LC 904 - Fruit Into Baskets

You have two baskets, and each basket can carry any quantity of fruit, but
you want each basket to only carry one type of fruit. Given an integer array
fruits where fruits[i] is the type of fruit the ith tree produces, return the
maximum number of fruits you can pick (must pick from a contiguous subarray).

Equivalent to: longest subarray with at most 2 distinct values.

Examples:
```text
Input: fruits = [1,2,1]
Output: 3

Input: fruits = [0,1,2,2]
Output: 3

Input: fruits = [1,2,3,2,2]
Output: 4
```

Constraints:
- 1 <= fruits.length <= 10^5
- 0 <= fruits[i] < fruits.length
"""

from typing import List
from collections import defaultdict


class Solution:
    def totalFruit(self, fruits: List[int]) -> int:
        # Sliding window with freq map — at most 2 distinct fruit types
        count = defaultdict(int)
        left = 0
        result = 0

        for right in range(len(fruits)):
            count[fruits[right]] += 1

            # More than 2 distinct types — shrink window from left
            while len(count) > 2:
                count[fruits[left]] -= 1
                if count[fruits[left]] == 0:
                    del count[fruits[left]]
                left += 1

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

        return result


if __name__ == '__main__':
    s = Solution()
    assert s.totalFruit([1,2,1]) == 3
    assert s.totalFruit([0,1,2,2]) == 3
    assert s.totalFruit([1,2,3,2,2]) == 4
    assert s.totalFruit([3,3,3,1,2,1,1,2,3,3,4]) == 5
    print("All tests passed.")
Longest Repeating Character Replacement ▼ expand
class Solution:
    """
    ## 424. Longest Repeating Character Replacement

    You are given a string `s` and an integer `k`. You can choose any character of the
    string and change it to any other uppercase English character. You can perform this
    operation at most `k` times.

    Return the length of the longest substring containing the same letter you can get
    after performing the above operations.

    ### Examples

    ```text
    Input:  s = "ABAB", k = 2
    Output: 4
    Explanation: Replace the two 'A's with 'B's or vice versa.

    Input:  s = "AABABBA", k = 1
    Output: 4
    Explanation: Replace the one 'A' in the middle with 'B' -> "AABBBBA" (length 4).
    ```

    ### Constraints

    - `1 <= s.length <= 10^5`
    - `s` consists of only uppercase English letters.
    - `0 <= k <= s.length`
    """

    def characterReplacement_brute(self, s: str, k: int) -> int:
        # Try all windows; window is valid if (size - max_freq) <= k — O(n^2)
        # O(n^2) time, O(1) space
        best = 0
        for i in range(len(s)):
            freq: dict[str, int] = {}
            for j in range(i, len(s)):
                freq[s[j]] = freq.get(s[j], 0) + 1
                window = j - i + 1
                if window - max(freq.values()) <= k:
                    best = max(best, window)
        return best

    def characterReplacement(self, s: str, k: int) -> int:
        # Expand right; window is valid if (size - max_freq) <= k replacements
        # When invalid, slide left by 1 (shrink window, don't reset max_freq)
        # max_freq only increases — we only care about windows larger than current best
        # O(n) time, O(1) space
        freq: dict[str, int] = {}
        left = max_freq = best = 0
        for right, ch in enumerate(s):
            freq[ch] = freq.get(ch, 0) + 1
            max_freq = max(max_freq, freq[ch])
            window = right - left + 1
            if window - max_freq > k:
                freq[s[left]] -= 1
                left += 1
            best = max(best, right - left + 1)
        return best


if __name__ == "__main__":
    sol = Solution()
    assert sol.characterReplacement("ABAB", 2) == 4
    assert sol.characterReplacement("AABABBA", 1) == 4
    assert sol.characterReplacement("A", 0) == 1
    assert sol.characterReplacement_brute("ABAB", 2) == 4
    assert sol.characterReplacement_brute("AABABBA", 1) == 4
    print("All tests passed.")
Longest Substring With At Most K Distinct Characters ▼ expand
"""
LC 340 - Longest Substring with At Most K Distinct Characters

Given a string s and an integer k, return the length of the longest
substring that contains at most k distinct characters.

Examples:
```text
Input: s = "eceba", k = 2
Output: 3

Input: s = "aa", k = 1
Output: 2

Input: s = "aabbcc", k = 2
Output: 4
```

Constraints:
- 1 <= s.length <= 5 * 10^4
- 0 <= k <= 50
"""

from collections import defaultdict


class Solution:
    def lengthOfLongestSubstringKDistinct(self, s: str, k: int) -> int:
        # Sliding window with freq map
        # Expand right always, shrink left when distinct count > k
        if k == 0:
            return 0

        count = defaultdict(int)
        left = 0
        result = 0

        for right in range(len(s)):
            count[s[right]] += 1

            # Too many distinct chars — shrink from left
            while len(count) > k:
                count[s[left]] -= 1
                if count[s[left]] == 0:
                    del count[s[left]]
                left += 1

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

        return result


if __name__ == '__main__':
    s = Solution()
    assert s.lengthOfLongestSubstringKDistinct("eceba", 2) == 3
    assert s.lengthOfLongestSubstringKDistinct("aa", 1) == 2
    assert s.lengthOfLongestSubstringKDistinct("aabbcc", 2) == 4
    assert s.lengthOfLongestSubstringKDistinct("abc", 0) == 0
    print("All tests passed.")
Longest Substring Without Repeating Characters ▼ expand
class Solution:
    """
    ## 3. Longest Substring Without Repeating Characters

    Given a string `s`, find the length of the longest substring without repeating
    characters.

    ### Examples

    ```text
    Input:  s = "abcabcbb"
    Output: 3
    Explanation: "abc" is the longest substring without repeating characters.

    Input:  s = "bbbbb"
    Output: 1
    Explanation: "b" is the longest substring.

    Input:  s = "pwwkew"
    Output: 3
    Explanation: "wke" is the longest substring.
    ```

    ### Constraints

    - `0 <= s.length <= 5 * 10^4`
    - `s` consists of English letters, digits, symbols, and spaces.
    """

    def lengthOfLongestSubstring_brute(self, s: str) -> int:
        # Try all substrings; check uniqueness via set — O(n^3)
        # O(n^3) time, O(min(n, m)) space where m is charset size
        best = 0
        for i in range(len(s)):
            for j in range(i + 1, len(s) + 1):
                if len(set(s[i:j])) == j - i:
                    best = max(best, j - i)
        return best

    def lengthOfLongestSubstring(self, s: str) -> int:
        # Slide right pointer; if char already in window, jump left past its last occurrence
        # char_idx stores the most recent index of each character
        # O(n) time, O(min(n, m)) space
        char_idx: dict[str, int] = {}
        left = best = 0
        for right, ch in enumerate(s):
            if ch in char_idx and char_idx[ch] >= left:
                left = char_idx[ch] + 1
            char_idx[ch] = right
            best = max(best, right - left + 1)
        return best


if __name__ == "__main__":
    sol = Solution()
    assert sol.lengthOfLongestSubstring("abcabcbb") == 3
    assert sol.lengthOfLongestSubstring("bbbbb") == 1
    assert sol.lengthOfLongestSubstring("pwwkew") == 3
    assert sol.lengthOfLongestSubstring("") == 0
    assert sol.lengthOfLongestSubstring_brute("abcabcbb") == 3
    assert sol.lengthOfLongestSubstring_brute("bbbbb") == 1
    print("All tests passed.")
Max Consecutive Ones Iii ▼ expand
"""
LC 1004 - Max Consecutive Ones III

Given a binary array nums and an integer k, return the maximum number of
consecutive 1s in the array if you can flip at most k 0s.

Examples:
```text
Input: nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2
Output: 6

Input: nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], k = 3
Output: 10
```

Constraints:
- 1 <= nums.length <= 10^5
- nums[i] is either 0 or 1
- 0 <= k <= nums.length
"""

from typing import List


class Solution:
    def longestOnes(self, nums: List[int], k: int) -> int:
        # Sliding window: expand right always, shrink left when zeros > k
        left = 0
        zeros = 0
        result = 0

        for right in range(len(nums)):
            if nums[right] == 0:
                zeros += 1

            # Window has more than k zeros — shrink from left
            while zeros > k:
                if nums[left] == 0:
                    zeros -= 1
                left += 1

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

        return result


if __name__ == '__main__':
    s = Solution()
    assert s.longestOnes([1,1,1,0,0,0,1,1,1,1,0], 2) == 6
    assert s.longestOnes([0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], 3) == 10
    assert s.longestOnes([1,1,1], 0) == 3
    assert s.longestOnes([0,0,0], 0) == 0
    print("All tests passed.")
Maximum Points You Can Obtain From Cards ▼ expand
"""
LC 1423 - Maximum Points You Can Obtain from Cards

There are several cards arranged in a row. In each step you may take one
card from the beginning or end of the row. You take exactly k cards.
Return the maximum score you can obtain.

Examples:
```text
Input: cardPoints = [1,2,3,4,5,6,1], k = 3
Output: 12

Input: cardPoints = [2,2,2], k = 2
Output: 4

Input: cardPoints = [9,7,7,9,7,7,9], k = 7
Output: 55
```

Constraints:
- 1 <= cardPoints.length <= 10^5
- 1 <= cardPoints[i] <= 10^4
- 1 <= k <= cardPoints.length
"""

from typing import List


class Solution:
    def maxScore(self, cardPoints: List[int], k: int) -> int:
        # Approach 1: minimize the sum of the middle window of size n-k
        # Taking k from edges = leaving n-k in the middle
        n = len(cardPoints)
        window = n - k

        if window == 0:
            return sum(cardPoints)

        # Compute sum of first window
        curr = sum(cardPoints[:window])
        min_mid = curr

        for i in range(window, n):
            curr += cardPoints[i] - cardPoints[i - window]
            min_mid = min(min_mid, curr)

        return sum(cardPoints) - min_mid

    def maxScore_prefix(self, cardPoints: List[int], k: int) -> int:
        # Approach 2: prefix sum from both ends — try all splits of k cards
        # take i from front, k-i from back
        total = sum(cardPoints)
        n = len(cardPoints)
        prefix = [0] * (k + 1)
        suffix = [0] * (k + 1)

        for i in range(1, k + 1):
            prefix[i] = prefix[i - 1] + cardPoints[i - 1]
            suffix[i] = suffix[i - 1] + cardPoints[n - i]

        return max(prefix[i] + suffix[k - i] for i in range(k + 1))


if __name__ == '__main__':
    s = Solution()
    assert s.maxScore([1,2,3,4,5,6,1], 3) == 12
    assert s.maxScore([2,2,2], 2) == 4
    assert s.maxScore([9,7,7,9,7,7,9], 7) == 55
    assert s.maxScore_prefix([1,2,3,4,5,6,1], 3) == 12
    assert s.maxScore_prefix([2,2,2], 2) == 4
    print("All tests passed.")
Minimum Size Subarray Sum ▼ expand
from typing import List


class Solution:
    """
    ## 209. Minimum Size Subarray Sum

    Given an array of positive integers `nums` and a positive integer `target`, return
    the minimal length of a subarray whose sum is greater than or equal to `target`.
    If there is no such subarray, return `0` instead.

    ### Examples

    ```text
    Input:  target = 7, nums = [2, 3, 1, 2, 4, 3]
    Output: 2
    Explanation: [4, 3] has the minimal length under the problem constraint.

    Input:  target = 4, nums = [1, 4, 4]
    Output: 1

    Input:  target = 11, nums = [1, 1, 1, 1, 1, 1, 1, 1]
    Output: 0
    ```

    ### Constraints

    - `1 <= target <= 10^9`
    - `1 <= nums.length <= 10^5`
    - `1 <= nums[i] <= 10^4`
    """

    def minSubArrayLen_brute(self, target: int, nums: List[int]) -> int:
        # Expand from each start until sum >= target — O(n^2)
        # O(n^2) time, O(1) space
        best = float("inf")
        for i in range(len(nums)):
            total = 0
            for j in range(i, len(nums)):
                total += nums[j]
                if total >= target:
                    best = min(best, j - i + 1)
                    break
        return 0 if best == float("inf") else best

    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        # Expand right to grow sum; shrink left while sum still >= target
        # Each element is added and removed at most once — O(n) total
        # O(n) time, O(1) space
        left = total = 0
        best = float("inf")
        for right, num in enumerate(nums):
            total += num
            while total >= target:
                best = min(best, right - left + 1)
                total -= nums[left]
                left += 1
        return 0 if best == float("inf") else best


if __name__ == "__main__":
    sol = Solution()
    assert sol.minSubArrayLen(7, [2, 3, 1, 2, 4, 3]) == 2
    assert sol.minSubArrayLen(4, [1, 4, 4]) == 1
    assert sol.minSubArrayLen(11, [1, 1, 1, 1, 1, 1, 1, 1]) == 0
    assert sol.minSubArrayLen_brute(7, [2, 3, 1, 2, 4, 3]) == 2
    assert sol.minSubArrayLen_brute(11, [1, 1, 1, 1, 1, 1, 1, 1]) == 0
    print("All tests passed.")
Minimum Window Subsequence ▼ expand
"""
LC 727 - Minimum Window Subsequence

Given strings s1 and s2, return the minimum window in s1 such that s2 is
a subsequence of that window. If no such window exists, return "".

Examples:
```text
Input: s1 = "abcdebdde", s2 = "bde"
Output: "bcde"

Input: s1 = "jmeqksfrsdcmsiwvaovztaqenprpvnbstl", s2 = "u"
Output: ""
```

Constraints:
- 1 <= s1.length <= 2 * 10^4
- 1 <= s2.length <= 100
- s1 and s2 consist of lowercase English letters
"""


class Solution:
    def minWindow(self, s1: str, s2: str) -> str:
        # Approach 1: two-pointer forward + backward
        # Forward: find a window where s2 is a subsequence
        # Backward: shrink from right to minimize the window
        n, m = len(s1), len(s2)
        result = ""

        i = 0  # pointer into s1
        while i < n:
            # Forward pass: match s2 left-to-right starting from i
            j = 0
            while i < n and j < m:
                if s1[i] == s2[j]:
                    j += 1
                i += 1

            if j < m:
                break  # s2 not found, no more windows possible

            # i is now one past the end of the window; back up
            end = i - 1

            # Backward pass: shrink from end to find tightest window
            j = m - 1
            k = end
            while k >= 0 and j >= 0:
                if s1[k] == s2[j]:
                    j -= 1
                k -= 1

            start = k + 1  # tightest window start

            if not result or end - start + 1 < len(result):
                result = s1[start:end + 1]

            # Restart search from start + 1 to find next candidate
            i = start + 1

        return result

    def minWindow_dp(self, s1: str, s2: str) -> str:
        # Approach 2: DP
        # dp[i][j] = start index in s1 such that s1[dp[i][j]..i] contains s2[0..j-1]
        n, m = len(s1), len(s2)
        # dp[j] = earliest start in s1 to match s2[:j] ending at current s1 position
        dp = [-1] * (m + 1)
        dp[0] = 0  # empty s2 matched at any position
        result = ""

        for i in range(n):
            # Traverse s2 backwards to avoid using updated values
            for j in range(min(i + 1, m), 0, -1):
                if s1[i] == s2[j - 1] and dp[j - 1] != -1:
                    dp[j] = dp[j - 1]
            # Reset dp[0] for next character
            dp[0] = i + 1

            if dp[m] != -1:
                window = s1[dp[m]:i + 1]
                if not result or len(window) < len(result):
                    result = window

        return result


if __name__ == '__main__':
    s = Solution()
    assert s.minWindow("abcdebdde", "bde") == "bcde"
    assert s.minWindow("jmeqksfrsdcmsiwvaovztaqenprpvnbstl", "u") == ""
    assert s.minWindow("abcde", "ace") == "abcde"
    assert s.minWindow_dp("abcdebdde", "bde") == "bcde"
    assert s.minWindow_dp("jmeqksfrsdcmsiwvaovztaqenprpvnbstl", "u") == ""
    print("All tests passed.")
Minimum Window Substring ▼ expand
from collections import Counter


class Solution:
    """
    ## 76. Minimum Window Substring

    Given two strings `s` and `t` of lengths `m` and `n` respectively, return the
    minimum window substring of `s` such that every character in `t` (including
    duplicates) is included in the window. If there is no such substring, return the
    empty string `""`.

    ### Examples

    ```text
    Input:  s = "ADOBECODEBANC", t = "ABC"
    Output: "BANC"

    Input:  s = "a", t = "a"
    Output: "a"

    Input:  s = "a", t = "aa"
    Output: ""
    ```

    ### Constraints

    - `1 <= m, n <= 10^5`
    - `s` and `t` consist of uppercase and lowercase English letters.
    """

    def minWindow_brute(self, s: str, t: str) -> str:
        # Try all substrings starting at i; stop as soon as t is covered — O(n^2*m)
        # O(n^2 * m) time, O(m) space
        need = Counter(t)
        best = ""
        for i in range(len(s)):
            have: dict[str, int] = {}
            for j in range(i, len(s)):
                have[s[j]] = have.get(s[j], 0) + 1
                if all(have.get(c, 0) >= need[c] for c in need):
                    if not best or j - i + 1 < len(best):
                        best = s[i:j + 1]
                    break
        return best

    def minWindow(self, s: str, t: str) -> str:
        # 'formed' counts how many unique chars in t are satisfied in current window
        # Expand right until all chars satisfied; then shrink left to minimize window
        # Track best window length and start index throughout
        # O(n + m) time, O(m) space
        if not t:
            return ""
        need = Counter(t)
        have: dict[str, int] = {}
        formed = 0
        required = len(need)
        left = 0
        best_len, best_l = float("inf"), 0
        for right, ch in enumerate(s):
            have[ch] = have.get(ch, 0) + 1
            if ch in need and have[ch] == need[ch]:
                formed += 1
            while formed == required:
                if right - left + 1 < best_len:
                    best_len, best_l = right - left + 1, left
                lch = s[left]
                have[lch] -= 1
                if lch in need and have[lch] < need[lch]:
                    formed -= 1
                left += 1
        return "" if best_len == float("inf") else s[best_l:best_l + best_len]


if __name__ == "__main__":
    sol = Solution()
    assert sol.minWindow("ADOBECODEBANC", "ABC") == "BANC"
    assert sol.minWindow("a", "a") == "a"
    assert sol.minWindow("a", "aa") == ""
    assert sol.minWindow_brute("ADOBECODEBANC", "ABC") == "BANC"
    assert sol.minWindow_brute("a", "a") == "a"
    assert sol.minWindow_brute("a", "aa") == ""
    print("All tests passed.")
Number Of Substrings Containing All Three Characters ▼ expand
"""
LC 1358 - Number of Substrings Containing All Three Characters

Given a string s consisting only of characters 'a', 'b', and 'c', return
the number of substrings containing at least one occurrence of all three.

Examples:
```text
Input: s = "abcabc"
Output: 10

Input: s = "aaacb"
Output: 3

Input: s = "abc"
Output: 1
```

Constraints:
- 3 <= s.length <= 5 * 10^4
- s consists only of 'a', 'b', 'c'
"""


class Solution:
    def numberOfSubstrings(self, s: str) -> int:
        # Sliding window: for each right, find smallest left where all 3 present
        # Every extension of that window to the left also forms a valid substring
        # so add (left + 1) to result
        count = {'a': 0, 'b': 0, 'c': 0}
        left = 0
        result = 0

        for right in range(len(s)):
            count[s[right]] += 1

            # Shrink from left while all 3 chars are present
            while count['a'] > 0 and count['b'] > 0 and count['c'] > 0:
                count[s[left]] -= 1
                left += 1

            # left is now one past the smallest valid window end
            # so there are `left` valid starting positions for this right
            result += left

        return result


if __name__ == '__main__':
    s = Solution()
    assert s.numberOfSubstrings("abcabc") == 10
    assert s.numberOfSubstrings("aaacb") == 3
    assert s.numberOfSubstrings("abc") == 1
    print("All tests passed.")
Permutation In String ▼ expand
class Solution:
    """
    ## 567. Permutation in String

    Given two strings `s1` and `s2`, return `true` if `s2` contains a permutation of
    `s1`, or `false` otherwise.

    In other words, return `true` if one of `s1`'s permutations is a substring of `s2`.

    ### Examples

    ```text
    Input:  s1 = "ab", s2 = "eidbaooo"
    Output: true
    Explanation: s2 contains "ba", a permutation of s1.

    Input:  s1 = "ab", s2 = "eidboaoo"
    Output: false
    ```

    ### Constraints

    - `1 <= s1.length, s2.length <= 10^4`
    - `s1` and `s2` consist of lowercase English letters.
    """

    def checkInclusion_brute(self, s1: str, s2: str) -> bool:
        # Sort each window of length len(s1) and compare to sorted s1 — O(n*m log m)
        # O(n * m log m) time, O(m) space
        target = sorted(s1)
        m = len(s1)
        for i in range(len(s2) - m + 1):
            if sorted(s2[i:i + m]) == target:
                return True
        return False

    def checkInclusion(self, s1: str, s2: str) -> bool:
        # Fixed-size window of length len(s1); use 26-element frequency arrays
        # Slide window right: add new char, remove char that fell out of window
        # If frequency arrays match, current window is a permutation of s1
        # O(n) time, O(1) space (fixed 26-char alphabet)
        if len(s1) > len(s2):
            return False
        need = [0] * 26
        have = [0] * 26
        for ch in s1:
            need[ord(ch) - ord('a')] += 1
        m = len(s1)
        for i, ch in enumerate(s2):
            have[ord(ch) - ord('a')] += 1
            if i >= m:
                have[ord(s2[i - m]) - ord('a')] -= 1
            if have == need:
                return True
        return False


if __name__ == "__main__":
    sol = Solution()
    assert sol.checkInclusion("ab", "eidbaooo") == True
    assert sol.checkInclusion("ab", "eidboaoo") == False
    assert sol.checkInclusion("a", "a") == True
    assert sol.checkInclusion_brute("ab", "eidbaooo") == True
    assert sol.checkInclusion_brute("ab", "eidboaoo") == False
    print("All tests passed.")
Sliding Window Maximum ▼ expand
from typing import List
from collections import deque


class Solution:
    """
    ## 239. Sliding Window Maximum

    You are given an array of integers `nums`, there is a sliding window of size `k`
    which is moving from the very left of the array to the very right. You can only see
    the `k` numbers in the window. Each time the sliding window moves right by one
    position.

    Return the max sliding window — an array of the maximum values in each window
    position.

    ### Examples

    ```text
    Input:  nums = [1, 3, -1, -3, 5, 3, 6, 7], k = 3
    Output: [3, 3, 5, 5, 6, 7]

    Input:  nums = [1], k = 1
    Output: [1]
    ```

    ### Constraints

    - `1 <= nums.length <= 10^5`
    - `-10^4 <= nums[i] <= 10^4`
    - `1 <= k <= nums.length`
    """

    def maxSlidingWindow_brute(self, nums: List[int], k: int) -> List[int]:
        # For each window position, compute max with built-in — O(n*k)
        # O(n * k) time, O(1) extra space
        return [max(nums[i:i + k]) for i in range(len(nums) - k + 1)]

    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        # Monotonic deque: front always holds index of current window's max
        # Pop from back any index whose value <= current (they can never be max)
        # Pop from front if that index has slid out of the current window
        # O(n) time, O(k) space
        dq: deque[int] = deque()  # stores indices, decreasing by value
        result: List[int] = []
        for i, num in enumerate(nums):
            while dq and nums[dq[-1]] <= num:
                dq.pop()
            dq.append(i)
            if dq[0] < i - k + 1:
                dq.popleft()
            if i >= k - 1:
                result.append(nums[dq[0]])
        return result


if __name__ == "__main__":
    sol = Solution()
    assert sol.maxSlidingWindow([1, 3, -1, -3, 5, 3, 6, 7], 3) == [3, 3, 5, 5, 6, 7]
    assert sol.maxSlidingWindow([1], 1) == [1]
    assert sol.maxSlidingWindow([1, -1], 1) == [1, -1]
    assert sol.maxSlidingWindow_brute([1, 3, -1, -3, 5, 3, 6, 7], 3) == [3, 3, 5, 5, 6, 7]
    assert sol.maxSlidingWindow_brute([1], 1) == [1]
    print("All tests passed.")
Subarrays With K Different Integers ▼ expand
"""
LC 992 - Subarrays with K Different Integers

Given an integer array nums and an integer k, return the number of
good subarrays. A good subarray has exactly k different integers.

Examples:
```text
Input: nums = [1,2,1,2,3], k = 2
Output: 7

Input: nums = [1,2,1,3,4], k = 3
Output: 3
```

Constraints:
- 1 <= nums.length <= 2 * 10^4
- 1 <= nums[i] <= nums.length
- 1 <= k <= nums.length
"""

from typing import List
from collections import defaultdict


class Solution:
    def subarraysWithKDistinct(self, nums: List[int], k: int) -> int:
        # exactly(k) = atMost(k) - atMost(k-1)
        # Each atMost is a standard sliding window counting subarrays
        def atMost(limit: int) -> int:
            count = defaultdict(int)
            left = res = 0
            for right in range(len(nums)):
                count[nums[right]] += 1
                # shrink until distinct count <= limit
                while len(count) > limit:
                    count[nums[left]] -= 1
                    if count[nums[left]] == 0:
                        del count[nums[left]]
                    left += 1
                # all subarrays ending at right with at most `limit` distinct
                res += right - left + 1
            return res

        return atMost(k) - atMost(k - 1)


if __name__ == '__main__':
    s = Solution()
    assert s.subarraysWithKDistinct([1,2,1,2,3], 2) == 7
    assert s.subarraysWithKDistinct([1,2,1,3,4], 3) == 3
    assert s.subarraysWithKDistinct([1,1,1,1], 1) == 10
    print("All tests passed.")

Sliding Window

Avoid nested loops by maintaining a dynamic window

2
L
0
1
1
5
2
1
3
3
4
2
5
Press "Next Step" to start. Finding minimum subarray with sum ≥ target.