Algorithm Search⌘K

Sliding window for positive sums in Python

Grow the right end until the sum reaches s, then shrink the left end; runs in linear time because values are positive.

File
sliding_window_positive_sum.py, 78 lines
Level
I0 not checked yet
Toolchain
Python 3.14, pytest 9
Test framework
pytest
Run the tests
python3 -m pytest
sliding_window_positive_sum.pyRawGitHub
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/sliding-window-positive-sum/. The authors are listed in prov/7FDZS3.jsonl.

from typing import Optional, Union

Number = Union[int, float]


def min_window_length(a: list[Number], s: Number) -> int:
    """Return the length of the shortest subarray of a with sum >= s, or 0 if there is none.

    Precondition: every item of a is >= 0 and s > 0 (raises ValueError otherwise).
    Cost: O(n) time, O(1) extra space.
    """
    if s <= 0:
        raise ValueError("the threshold s must be positive")
    if any(x < 0 for x in a):
        raise ValueError("the items must not be negative")
    left = 0  # @line L1
    total: Number = 0
    best: Optional[int] = None
    for right in range(len(a)):  # @line L2
        total += a[right]  # @line L3
        while total >= s:  # @line L4
            length = right - left + 1  # @line L5
            if best is None or length < best:
                best = length
            total -= a[left]  # @line L6
            left += 1
    if best is None:  # @line L7
        return 0
    return best  # @line L8


def min_window(a: list[Number], s: Number) -> Optional[tuple[int, int]]:
    """Return (l, r), 0-based and inclusive, of a shortest subarray with sum >= s.

    Among the shortest windows, the one with the earliest end is returned.
    Returns None if there is no such window. Same precondition and cost as
    min_window_length.
    """
    if s <= 0:
        raise ValueError("the threshold s must be positive")
    if any(x < 0 for x in a):
        raise ValueError("the items must not be negative")
    left = 0
    total: Number = 0
    found: Optional[tuple[int, int]] = None
    for right in range(len(a)):
        total += a[right]
        while total >= s:
            if found is None or right - left < found[1] - found[0]:
                found = (left, right)
            total -= a[left]
            left += 1
    return found


def count_windows_below(a: list[Number], s: Number) -> int:
    """Return the number of non-empty subarrays of a with sum < s.

    Precondition: every item of a is >= 0 and s > 0.
    Cost: O(n) time, O(1) extra space.
    """
    if s <= 0:
        raise ValueError("the threshold s must be positive")
    if any(x < 0 for x in a):
        raise ValueError("the items must not be negative")
    left = 0
    total: Number = 0
    count = 0
    for right in range(len(a)):
        total += a[right]
        while total >= s:
            total -= a[left]
            left += 1
        count += right - left + 1
    return count
test_sliding_window_positive_sum.py 60 lines
# SPDX-License-Identifier: MIT
# Part of https://algorithm.tamnd.com/sliding-window-positive-sum/. The authors are listed in prov/7FDZS3.jsonl.

import random

import pytest

from sliding_window_positive_sum import count_windows_below, min_window, min_window_length


def brute(a: list[int], s: int) -> int:
    best = 0
    for i in range(len(a)):
        for j in range(i, len(a)):
            if sum(a[i : j + 1]) >= s and (best == 0 or j - i + 1 < best):
                best = j - i + 1
    return best


def test_example() -> None:
    assert min_window_length([2, 3, 1, 2, 4, 3], 7) == 2
    assert min_window([2, 3, 1, 2, 4, 3], 7) == (4, 5)


def test_edges() -> None:
    assert min_window_length([], 5) == 0
    assert min_window([], 5) is None
    assert min_window_length([1, 1, 1], 4) == 0
    assert min_window_length([1, 1, 1], 3) == 3
    assert min_window_length([7], 7) == 1
    assert min_window_length([1, 4, 4], 4) == 1
    assert min_window([1, 4, 4], 4) == (1, 1)
    assert min_window_length([0, 0, 5, 0], 5) == 1
    assert min_window_length([0.5, 0.25, 0.25], 1.0) == 3
    assert count_windows_below([10, 5, 2, 6], 100) == 10
    with pytest.raises(ValueError):
        min_window_length([1, 2], 0)
    with pytest.raises(ValueError):
        min_window_length([5, -10, 4, 4], 8)


def test_against_brute_force() -> None:
    rng = random.Random(20261105)
    for _ in range(2000):
        n = rng.randrange(0, 20)
        a = [rng.randrange(0, 10) for _ in range(n)]
        s = rng.randrange(1, 40)
        expected = brute(a, s)
        assert min_window_length(a, s) == expected
        w = min_window(a, s)
        if expected == 0:
            assert w is None
        else:
            assert w is not None
            assert w[1] - w[0] + 1 == expected
            assert sum(a[w[0] : w[1] + 1]) >= s
        below = sum(
            1 for i in range(n) for j in range(i, n) if sum(a[i : j + 1]) < s
        )
        assert count_windows_below(a, s) == below

What the next level needs

  • the tests did not run in this build

Read the full page about Sliding window for positive sums