Repository: speckit (difficulty: medium)

Issue:
lis_length overcounts whenever the input contains duplicate values. The subsequence is required to be strictly increasing, but the function computes the length of the longest non-decreasing subsequence instead.

Observed vs expected:
- lis_length([3, 3, 3]) returns 3; expected 1 (a strictly increasing subsequence can never use equal values twice)
- lis_length([1, 2, 2, 3]) returns 4; expected 3 (for example [1, 2, 3])
- lis_length([2, 2]) returns 2; expected 1
- lis_length([1, 3, 2, 3, 3]) returns 4; expected 3

Inputs without duplicates already come out right, for example lis_length([10, 9, 2, 5, 3, 7, 101, 18]) == 4, and the empty list must keep returning 0. Please keep the O(n log n) patience sorting approach; only the strictness of the comparison is wrong.

Source files:
core.py: """Longest strictly increasing subsequence length via patience sorting.

lis_length(nums) returns the length of the longest strictly increasing
subsequence of the list ``nums``. A subsequence keeps the original
order but need not be contiguous, and strictly increasing means every
chosen element is strictly greater than the previous one, so duplicate
values can never sit together in one subsequence: the answer for
[3, 3, 3] is 1.

- The empty list returns 0.
- Values are integers; any totally ordered values behave the same.
- Runs in O(n log n) using the patience sorting tails technique.
"""

from bisect import bisect_right


def lis_length(nums):
    """Return the length of the longest strictly increasing subsequence."""
    tails = []
    for value in nums:
        position = bisect_right(tails, value)
        if position == len(tails):
            tails.append(value)
        else:
            tails[position] = value
    return len(tails)


Test files:
test_core.py: """Tests for the strictly increasing subsequence length."""

import random

from core import lis_length


def reference_lis(nums):
    # Independent O(n^2) dynamic program over "longest strict increase
    # ending at index i"; the oracle for every case in this file.
    best = 0
    lengths = []
    for i, value in enumerate(nums):
        current = 1
        for j in range(i):
            if nums[j] < value and lengths[j] + 1 > current:
                current = lengths[j] + 1
        lengths.append(current)
        if current > best:
            best = current
    return best


def test_pinned_duplicate_heavy_cases():
    assert lis_length([]) == 0
    assert lis_length([7]) == 1
    assert lis_length([3, 3, 3]) == 1
    assert lis_length([2, 2]) == 1
    assert lis_length([1, 2, 2, 3]) == 3
    assert lis_length([1, 3, 2, 3, 3]) == 3
    assert lis_length([5, 5, 5, 6]) == 2
    assert lis_length([1, 1, 2, 2, 3, 3]) == 3
    assert lis_length([10, 9, 2, 5, 3, 7, 101, 18]) == 4
    assert lis_length([4, 3, 2, 1]) == 1


def test_random_cross_check_against_quadratic_dp():
    rng = random.Random(20260702)
    for _ in range(240):
        n = rng.randint(0, 60)
        nums = [rng.randint(0, 9) for _ in range(n)]
        assert lis_length(nums) == reference_lis(nums), nums


def test_large_duplicate_heavy_case():
    rng = random.Random(987654321)
    nums = [rng.randint(0, 40) for _ in range(1500)]
    assert lis_length(nums) == reference_lis(nums)


Patch:
--- a/core.py
+++ b/core.py
@@ -12,14 +12,14 @@
 - Runs in O(n log n) using the patience sorting tails technique.
 """
 
-from bisect import bisect_right
+from bisect import bisect_left
 
 
 def lis_length(nums):
     """Return the length of the longest strictly increasing subsequence."""
     tails = []
     for value in nums:
-        position = bisect_right(tails, value)
+        position = bisect_left(tails, value)
         if position == len(tails):
             tails.append(value)
         else: