A clear explanation of counting how many longest strictly increasing subsequences exist using dynamic programming.
Problem Restatement
We are given an integer array nums.
We need to return the number of longest increasing subsequences.
A subsequence is formed by deleting zero or more elements without changing the order of the remaining elements.
The subsequence must be strictly increasing. Equal adjacent values do not count as increasing. The official problem statement asks for the number of longest increasing subsequences and notes that the sequence must be strictly increasing.
Input and Output
| Item | Meaning |
|---|---|
| Input | An integer array nums |
| Output | Number of longest increasing subsequences |
| Subsequence rule | Keep original order, but may skip elements |
| Increasing rule | Each next value must be strictly greater |
| Count rule | Count all subsequences whose length equals the maximum LIS length |
Example function shape:
def findNumberOfLIS(nums: list[int]) -> int:
...Examples
Consider:
nums = [1, 3, 5, 4, 7]The longest increasing subsequences have length 4.
They are:
1, 3, 5, 7
1, 3, 4, 7So the answer is:
2Another example:
nums = [2, 2, 2, 2, 2]Since the sequence must be strictly increasing, no pair of equal values can be used together.
So the longest increasing subsequence length is 1.
Each single element is one valid subsequence.
There are five such subsequences, so the answer is:
5First Thought: Generate All Subsequences
A direct solution is to generate every subsequence, check whether it is strictly increasing, and track the longest length and count.
This is too slow.
An array of length n has:
2^nsubsequences.
We need to avoid enumerating them.
Key Insight
This is a longest increasing subsequence problem, but we need both:
- The best length.
- The number of ways to achieve that length.
For each index i, store two values:
| Array | Meaning |
|---|---|
length[i] | Length of the longest increasing subsequence ending at nums[i] |
count[i] | Number of longest increasing subsequences ending at nums[i] |
Every element can stand alone, so initially:
length[i] = 1
count[i] = 1Then for every earlier index j < i, if:
nums[j] < nums[i]we can append nums[i] after a subsequence ending at j.
Dynamic Programming Transition
For each pair j < i where nums[j] < nums[i], the candidate length is:
length[j] + 1There are two cases.
If this candidate is longer than the current best ending at i, replace the best:
length[i] = length[j] + 1
count[i] = count[j]If this candidate has the same length as the current best, add the number of ways:
count[i] += count[j]This works because each valid subsequence ending at j can be extended by nums[i].
Algorithm
- Let
n = len(nums). - Create:
length = [1] * ncount = [1] * n
- For each index
i:- Look at every previous index
j. - If
nums[j] < nums[i], try to extend subsequences ending atj.
- Look at every previous index
- After filling the arrays:
- Find
max_length = max(length). - Sum
count[i]for every index wherelength[i] == max_length.
- Find
- Return that sum.
Correctness
For each index i, length[i] records the length of the longest strictly increasing subsequence that ends at nums[i].
The initialization is correct because every element alone forms an increasing subsequence of length 1.
When nums[j] < nums[i], every increasing subsequence ending at j can be extended by nums[i]. This gives subsequences ending at i with length length[j] + 1.
If this length is greater than the current length[i], the algorithm replaces the previous best and copies count[j], because all previous shorter endings at i are no longer longest for index i.
If this length equals the current length[i], the algorithm adds count[j], because it has found additional distinct subsequences ending at i with the same best length.
The algorithm considers every earlier valid predecessor j, so after processing all pairs, each length[i] and count[i] is correct.
Every longest increasing subsequence in the full array ends at some index i where length[i] equals the global maximum length. Summing count[i] over those indices counts all longest increasing subsequences exactly once.
Therefore, the algorithm returns the correct number.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n^2) | For each index, we scan all previous indices |
| Space | O(n) | We store length and count arrays |
Implementation
from typing import List
class Solution:
def findNumberOfLIS(self, nums: List[int]) -> int:
n = len(nums)
length = [1] * n
count = [1] * n
for i in range(n):
for j in range(i):
if nums[j] >= nums[i]:
continue
candidate = length[j] + 1
if candidate > length[i]:
length[i] = candidate
count[i] = count[j]
elif candidate == length[i]:
count[i] += count[j]
max_length = max(length)
answer = 0
for i in range(n):
if length[i] == max_length:
answer += count[i]
return answerCode Explanation
We initialize every element as a subsequence of length 1:
length = [1] * n
count = [1] * nThen we process each index:
for i in range(n):For every earlier index:
for j in range(i):we check whether nums[j] can come before nums[i]:
if nums[j] >= nums[i]:
continueThe strict inequality matters. Equal values cannot be part of the same increasing subsequence.
If the pair is valid, the new length is:
candidate = length[j] + 1If this gives a longer subsequence ending at i, we replace the current best:
if candidate > length[i]:
length[i] = candidate
count[i] = count[j]If it gives another way to reach the same best length, we add the count:
elif candidate == length[i]:
count[i] += count[j]After all DP states are computed, we find the global LIS length:
max_length = max(length)Then sum the counts for every index that reaches that length:
for i in range(n):
if length[i] == max_length:
answer += count[i]Testing
def run_tests():
s = Solution()
assert s.findNumberOfLIS([1, 3, 5, 4, 7]) == 2
assert s.findNumberOfLIS([2, 2, 2, 2, 2]) == 5
assert s.findNumberOfLIS([1, 2, 3, 4]) == 1
assert s.findNumberOfLIS([4, 3, 2, 1]) == 4
assert s.findNumberOfLIS([1, 2, 4, 3, 5, 4, 7, 2]) == 3
assert s.findNumberOfLIS([1]) == 1
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
[1,3,5,4,7] | Standard example with two LIS |
[2,2,2,2,2] | Strict increasing rule makes each single element valid |
| Already increasing | Only one LIS using all elements |
| Strictly decreasing | Every single element is an LIS |
| Mixed values | Checks accumulation from multiple predecessors |
| Single element | Smallest valid input |