Before attempting this problem, you should be comfortable with:
The brute force approach explores every possible increasing subsequence by trying each element as a potential starting point. From each position, we recursively extend the subsequence by considering all future elements that are strictly greater than the current one. As we explore, we track the longest length found so far and count how many subsequences achieve that length. If we find a longer subsequence, we reset the count. If we find another subsequence of the same maximum length, we increment the count.
LIS to track the maximum length found and res to count subsequences of that length.i, start a dfs that treats element i as the beginning of a subsequence.dfs:LIS, update LIS and reset res to 1.LIS, increment res.j > i where nums[j] > nums[i].res after exploring all starting positions.class Solution:
def findNumberOfLIS(self, nums: List[int]) -> int:
LIS = 0
res = 0
def dfs(i, length):
nonlocal LIS, res
if LIS < length:
LIS = length
res = 1
elif LIS == length:
res += 1
for j in range(i + 1, len(nums)):
if nums[j] <= nums[i]:
continue
dfs(j, length + 1)
for i in range(len(nums)):
dfs(i, 1)
return resThe recursive solution has overlapping subproblems since we repeatedly compute the longest increasing subsequence starting from the same index. By using memoization, we can store the result for each starting position after computing it once. For each index, we store both the length of the longest increasing subsequence starting there and the count of such subsequences. When we revisit an index, we simply return the cached result instead of recomputing.
dp to store (maxLen, maxCnt) for each index.i, call dfs(i) which:maxLen = 1 and maxCnt = 1 (the element itself forms a subsequence of length 1).j > i where nums[j] > nums[i], recursively compute the result for j.1 + length[j] exceeds maxLen, update maxLen and set maxCnt to count[j].1 + length[j] equals maxLen, add count[j] to maxCnt.(maxLen, maxCnt).class Solution:
def findNumberOfLIS(self, nums: List[int]) -> int:
dp = {}
def dfs(i):
if i in dp:
return
maxLen = maxCnt = 1
for j in range(i + 1, len(nums)):
if nums[j] > nums[i]:
dfs(j)
length, count = dp[j]
if 1 + length > maxLen:
maxLen = length + 1
maxCnt = count
elif 1 + length == maxLen:
maxCnt += count
dp[i] = (maxLen, maxCnt)
lenLIS = res = 0
for i in range(len(nums)):
dfs(i)
maxLen, maxCnt = dp[i]
if maxLen > lenLIS:
lenLIS = maxLen
res = maxCnt
elif maxLen == lenLIS:
res += maxCnt
return resInstead of using recursion with memoization, we can iterate through the array in reverse order and build up the solution. For each position, we look at all positions to its right and determine the longest increasing subsequence that can be formed starting from the current position. This approach naturally ensures that when we process position i, all positions j > i have already been computed, giving us the information we need.
dp[i] = [maxLen, maxCnt] represents the longest increasing subsequence starting at index i and the count of such subsequences.n-1 to 0).i:maxLen = 1 and maxCnt = 1.j > i where nums[j] > nums[i]:dp[j][0] + 1 > maxLen, update maxLen and set maxCnt = dp[j][1].dp[j][0] + 1 == maxLen, add dp[j][1] to maxCnt.dp[i] = [maxLen, maxCnt].class Solution:
def findNumberOfLIS(self, nums: List[int]) -> int:
n = len(nums)
dp = [[0, 0] for _ in range(n)]
lenLIS, res = 0, 0
for i in range(n - 1, -1, -1):
maxLen, maxCnt = 1, 1
for j in range(i + 1, n):
if nums[j] > nums[i]:
length, count = dp[j]
if length + 1 > maxLen:
maxLen, maxCnt = length + 1, count
elif length + 1 == maxLen:
maxCnt += count
if maxLen > lenLIS:
lenLIS, res = maxLen, maxCnt
elif maxLen == lenLIS:
res += maxCnt
dp[i] = [maxLen, maxCnt]
return resThe O(n^2) DP solution can be optimized by using binary search and prefix sums. The key insight is to organize elements by the length of the longest increasing subsequence ending at them. For each length, we maintain a list of elements sorted in decreasing order along with cumulative counts. When processing a new element, we use binary search to find where it fits and to count how many subsequences of the previous length can be extended by this element.
dp as a list of lists, where dp[len] contains pairs (value, cumulative_count) representing elements that end an increasing subsequence of length len+1.bs1) to find the longest subsequence length this element can extend.bs2) on the previous length's list to count how many subsequences can be extended (those with values less than the current element).dp.class Solution:
def findNumberOfLIS(self, nums: List[int]) -> int:
n = len(nums)
dp = [[[0, 0], [nums[0], 1]]]
def bs1(num):
l, r = 0, len(dp) - 1
j = len(dp) - 1
while l <= r:
mid = (l + r) // 2
if dp[mid][-1][0] < num:
l = mid + 1
else:
j = mid
r = mid - 1
return j
def bs2(i, num):
if i < 0:
return 1
l, r = 1, len(dp[i]) - 1
j = 0
while l <= r:
mid = (l + r) // 2
if dp[i][mid][0] >= num:
j = mid
l = mid + 1
else:
r = mid - 1
return dp[i][-1][1] - dp[i][j][1]
LIS = 1
for i in range(1, n):
num = nums[i]
if num > dp[-1][-1][0]:
count = bs2(LIS - 1, num)
dp.append([[0, 0], [num, count]])
LIS += 1
else:
j = bs1(num)
count = bs2(j - 1, num)
dp[j].append([num, dp[j][-1][1] + count])
return dp[-1][-1][1]This problem requires counting how many subsequences achieve the maximum length, not just finding the length itself. A common mistake is to reuse standard LIS code that only tracks length. You must maintain both the length and the count of subsequences reaching that length at each position.
When finding a subsequence of the same maximum length, you should add to the count, not reset it. Only reset the count when you find a strictly longer subsequence. Confusing these cases leads to undercounting valid subsequences.
The problem asks for strictly increasing subsequences. Using nums[j] >= nums[i] instead of nums[j] > nums[i] will incorrectly include equal elements, leading to wrong answers on inputs with duplicate values.