673. Number of Longest Increasing Subsequence
Problem Description
Given an integer array nums
, you need to find and return the total count of longest increasing subsequences in the array.
A subsequence is a sequence that can be derived from the array by deleting some or no elements without changing the order of the remaining elements. An increasing subsequence means each element must be strictly greater than the previous one (no equal values allowed).
For example, if nums = [1, 3, 5, 4, 7]
:
- The longest increasing subsequences have length 4
- There are two such subsequences:
[1, 3, 5, 7]
and[1, 3, 4, 7]
- So the answer would be 2
The task is to count how many different longest increasing subsequences exist in the array. If multiple subsequences share the maximum length, count all of them.
Intuition
To solve this problem, we need to track two things simultaneously: the length of the longest increasing subsequence ending at each position, and how many such subsequences exist.
Think about building increasing subsequences step by step. For each element nums[i]
, we can either start a new subsequence with just this element, or extend existing subsequences that end with smaller values.
The key insight is that when we're at position i
, we need to look back at all previous positions j
where nums[j] < nums[i]
. Each of these positions represents a potential way to extend a subsequence.
For each valid previous position j
:
- If extending from
j
gives us a longer subsequence than we've found so far ending ati
, we've discovered a new optimal length. We update our length and reset our count to match the count from positionj
. - If extending from
j
gives us the same length as our current best, we've found additional ways to form subsequences of the optimal length. We add the count from positionj
to our current count.
This naturally leads to using two arrays: f[i]
to store the length of the longest subsequence ending at position i
, and cnt[i]
to store how many such subsequences exist.
As we process the entire array, we also maintain global tracking of the overall longest length found and accumulate counts whenever we encounter subsequences matching this maximum length. This way, we can return the total count of all longest increasing subsequences in the array.
The dynamic programming nature emerges from reusing previously computed results - we build upon the optimal solutions for earlier positions to find optimal solutions for later positions.
Learn more about Segment Tree and Dynamic Programming patterns.
Solution Approach
We implement a dynamic programming solution using two arrays to track the necessary information:
-
Initialize arrays: Create two arrays
f
andcnt
, both of sizen
:f[i]
stores the length of the longest increasing subsequence ending at positioni
cnt[i]
stores the count of such longest subsequences ending at positioni
- Initialize both arrays with value 1, since each element alone forms a subsequence of length 1
-
Track global maximum: Maintain variables
mx
(maximum length found) andans
(count of subsequences with maximum length) -
Build the DP solution: For each position
i
from 0 ton-1
:- Examine all previous positions
j
from 0 toi-1
- If
nums[j] < nums[i]
, we can extend the subsequence from positionj
:- If
f[i] < f[j] + 1
: We found a longer subsequence- Update
f[i] = f[j] + 1
(new length) - Set
cnt[i] = cnt[j]
(inherit the count from positionj
)
- Update
- If
f[i] == f[j] + 1
: We found another way to form a subsequence of the same optimal length- Add to the count:
cnt[i] += cnt[j]
- Add to the count:
- If
- Examine all previous positions
-
Update global answer: After processing all positions
j
for currenti
:- If
mx < f[i]
: We found a new global maximum length- Update
mx = f[i]
- Reset
ans = cnt[i]
- Update
- If
mx == f[i]
: We found more subsequences with the maximum length- Add to the answer:
ans += cnt[i]
- Add to the answer:
- If
-
Return result: After processing all positions,
ans
contains the total count of longest increasing subsequences
The time complexity is O(n²)
due to the nested loops, and space complexity is O(n)
for the two arrays.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [1, 3, 5, 4, 7]
:
Initial Setup:
- Arrays
f
andcnt
both start as[1, 1, 1, 1, 1]
(each element forms a subsequence of length 1) mx = 0
,ans = 0
Processing i = 0 (nums[0] = 1):
- No previous elements to check
- After processing:
f[0] = 1
,cnt[0] = 1
- Update globals:
mx = 1
,ans = 1
Processing i = 1 (nums[1] = 3):
- Check j = 0:
nums[0] = 1 < 3
✓f[1] = 1 < f[0] + 1 = 2
, so update:f[1] = 2
,cnt[1] = 1
- After processing:
f[1] = 2
,cnt[1] = 1
- Update globals:
mx = 2
,ans = 1
Processing i = 2 (nums[2] = 5):
- Check j = 0:
nums[0] = 1 < 5
✓f[2] = 1 < f[0] + 1 = 2
, so update:f[2] = 2
,cnt[2] = 1
- Check j = 1:
nums[1] = 3 < 5
✓f[2] = 2 < f[1] + 1 = 3
, so update:f[2] = 3
,cnt[2] = 1
- After processing:
f[2] = 3
,cnt[2] = 1
- Update globals:
mx = 3
,ans = 1
Processing i = 3 (nums[3] = 4):
- Check j = 0:
nums[0] = 1 < 4
✓f[3] = 1 < f[0] + 1 = 2
, so update:f[3] = 2
,cnt[3] = 1
- Check j = 1:
nums[1] = 3 < 4
✓f[3] = 2 < f[1] + 1 = 3
, so update:f[3] = 3
,cnt[3] = 1
- Check j = 2:
nums[2] = 5 > 4
✗ (skip) - After processing:
f[3] = 3
,cnt[3] = 1
- Update globals:
mx = 3
,ans = 1 + 1 = 2
(another subsequence of length 3)
Processing i = 4 (nums[4] = 7):
- Check j = 0:
nums[0] = 1 < 7
✓f[4] = 1 < f[0] + 1 = 2
, so update:f[4] = 2
,cnt[4] = 1
- Check j = 1:
nums[1] = 3 < 7
✓f[4] = 2 < f[1] + 1 = 3
, so update:f[4] = 3
,cnt[4] = 1
- Check j = 2:
nums[2] = 5 < 7
✓f[4] = 3 < f[2] + 1 = 4
, so update:f[4] = 4
,cnt[4] = 1
- Check j = 3:
nums[3] = 4 < 7
✓f[4] = 4 == f[3] + 1 = 4
, so add counts:cnt[4] = 1 + 1 = 2
- After processing:
f[4] = 4
,cnt[4] = 2
- Update globals:
mx = 4
,ans = 2
Final State:
f = [1, 2, 3, 3, 4]
(lengths of longest subsequences ending at each position)cnt = [1, 1, 1, 1, 2]
(counts of such subsequences)- Answer:
2
(two longest increasing subsequences of length 4)
The two subsequences are [1, 3, 5, 7]
and [1, 3, 4, 7]
, both captured by our count at position 4.
Solution Implementation
1class Solution:
2 def findNumberOfLIS(self, nums: List[int]) -> int:
3 n = len(nums)
4
5 # dp[i] represents the length of longest increasing subsequence ending at index i
6 dp = [1] * n
7
8 # count[i] represents the number of longest increasing subsequences ending at index i
9 count = [1] * n
10
11 max_length = 0
12 result = 0
13
14 # Iterate through each position as potential end of subsequence
15 for i in range(n):
16 # Check all previous elements that could form increasing subsequence
17 for j in range(i):
18 if nums[j] < nums[i]:
19 # Found a longer subsequence by extending from j to i
20 if dp[i] < dp[j] + 1:
21 dp[i] = dp[j] + 1
22 count[i] = count[j] # Inherit count from j
23 # Found another subsequence of same length
24 elif dp[i] == dp[j] + 1:
25 count[i] += count[j] # Add count from j
26
27 # Update global maximum length and corresponding count
28 if max_length < dp[i]:
29 max_length = dp[i]
30 result = count[i] # Reset result to current count
31 elif max_length == dp[i]:
32 result += count[i] # Add to existing count of max length subsequences
33
34 return result
35
1class Solution {
2 public int findNumberOfLIS(int[] nums) {
3 int n = nums.length;
4
5 // dp[i] represents the length of the longest increasing subsequence ending at index i
6 int[] dp = new int[n];
7
8 // count[i] represents the number of longest increasing subsequences ending at index i
9 int[] count = new int[n];
10
11 // Track the maximum length and the total count of all longest increasing subsequences
12 int maxLength = 0;
13 int result = 0;
14
15 // Process each element as a potential end of an increasing subsequence
16 for (int i = 0; i < n; i++) {
17 // Initialize: each element itself forms a subsequence of length 1
18 dp[i] = 1;
19 count[i] = 1;
20
21 // Check all previous elements to build longer subsequences
22 for (int j = 0; j < i; j++) {
23 // Only consider if we can extend the subsequence (increasing order)
24 if (nums[j] < nums[i]) {
25 if (dp[i] < dp[j] + 1) {
26 // Found a longer subsequence ending at i
27 dp[i] = dp[j] + 1;
28 count[i] = count[j]; // Inherit the count from j
29 } else if (dp[i] == dp[j] + 1) {
30 // Found another way to form the same length subsequence
31 count[i] += count[j]; // Add the count from j
32 }
33 }
34 }
35
36 // Update global maximum length and count
37 if (maxLength < dp[i]) {
38 // Found a new maximum length
39 maxLength = dp[i];
40 result = count[i];
41 } else if (maxLength == dp[i]) {
42 // Found another subsequence with the same maximum length
43 result += count[i];
44 }
45 }
46
47 return result;
48 }
49}
50
1class Solution {
2public:
3 int findNumberOfLIS(vector<int>& nums) {
4 int n = nums.size();
5 int maxLength = 0; // Maximum length of LIS found so far
6 int result = 0; // Total count of LIS with maximum length
7
8 // dp[i] stores the length of LIS ending at index i
9 vector<int> dp(n, 1);
10
11 // count[i] stores the number of LIS ending at index i
12 vector<int> count(n, 1);
13
14 // Iterate through each position as potential end of LIS
15 for (int i = 0; i < n; ++i) {
16 // Check all previous elements that can form LIS with current element
17 for (int j = 0; j < i; ++j) {
18 // If nums[j] can be part of increasing sequence ending at nums[i]
19 if (nums[j] < nums[i]) {
20 // Found a longer LIS ending at i through j
21 if (dp[i] < dp[j] + 1) {
22 dp[i] = dp[j] + 1;
23 count[i] = count[j]; // Inherit count from j
24 }
25 // Found another LIS of same length ending at i through j
26 else if (dp[i] == dp[j] + 1) {
27 count[i] += count[j]; // Add count from j
28 }
29 }
30 }
31
32 // Update global maximum length and count
33 if (maxLength < dp[i]) {
34 maxLength = dp[i];
35 result = count[i]; // Reset count to current position's count
36 }
37 else if (maxLength == dp[i]) {
38 result += count[i]; // Add current position's count to total
39 }
40 }
41
42 return result;
43 }
44};
45
1/**
2 * Finds the number of longest increasing subsequences in an array
3 * @param nums - The input array of numbers
4 * @returns The count of longest increasing subsequences
5 */
6function findNumberOfLIS(nums: number[]): number {
7 const length = nums.length;
8 let totalCount = 0;
9 let maxLength = 0;
10
11 // dp[i] represents the length of the longest increasing subsequence ending at index i
12 const dp: number[] = Array(length).fill(1);
13
14 // count[i] represents the number of longest increasing subsequences ending at index i
15 const count: number[] = Array(length).fill(1);
16
17 // Iterate through each position in the array
18 for (let i = 0; i < length; ++i) {
19 // Check all previous elements
20 for (let j = 0; j < i; ++j) {
21 // If current element is greater than previous element, we can extend the subsequence
22 if (nums[j] < nums[i]) {
23 if (dp[i] < dp[j] + 1) {
24 // Found a longer subsequence ending at i
25 dp[i] = dp[j] + 1;
26 count[i] = count[j];
27 } else if (dp[i] === dp[j] + 1) {
28 // Found another subsequence of the same length ending at i
29 count[i] += count[j];
30 }
31 }
32 }
33
34 // Update the global maximum length and count
35 if (maxLength < dp[i]) {
36 // Found a new maximum length
37 maxLength = dp[i];
38 totalCount = count[i];
39 } else if (maxLength === dp[i]) {
40 // Found another subsequence with the maximum length
41 totalCount += count[i];
42 }
43 }
44
45 return totalCount;
46}
47
Time and Space Complexity
The time complexity is O(n^2)
, where n
is the length of the array nums
. This is because we have two nested loops - the outer loop iterates through all indices i
from 0
to n-1
, and for each i
, the inner loop iterates through all indices j
from 0
to i-1
. This results in approximately n * (n-1) / 2
comparisons, which simplifies to O(n^2)
.
The space complexity is O(n)
, where n
is the length of the array nums
. The algorithm uses two auxiliary arrays f
and cnt
, each of size n
, to store the length of the longest increasing subsequence ending at each position and the count of such subsequences respectively. Additionally, a few constant space variables (mx
, ans
, i
, j
) are used, but these don't affect the overall space complexity. Therefore, the total space complexity is O(n) + O(n) + O(1) = O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall: Forgetting to Update Count When Finding Equal Length Subsequences
One of the most common mistakes in this problem is incorrectly handling the count array when multiple subsequences of the same length can end at position i
. Developers often forget to accumulate counts from all valid previous positions.
Incorrect Implementation:
for j in range(i):
if nums[j] < nums[i]:
if dp[i] < dp[j] + 1:
dp[i] = dp[j] + 1
count[i] = count[j] # Only storing the last count
This implementation overwrites count[i]
each time instead of accumulating when finding multiple paths of the same optimal length.
Correct Implementation:
for j in range(i):
if nums[j] < nums[i]:
if dp[i] < dp[j] + 1:
dp[i] = dp[j] + 1
count[i] = count[j] # Reset count for new maximum
elif dp[i] == dp[j] + 1:
count[i] += count[j] # Accumulate count for equal length
Pitfall: Incorrect Initialization of Count Array
Another mistake is initializing the count array to 0 instead of 1. Each element by itself forms a valid subsequence of length 1, so there's always at least one subsequence ending at each position.
Incorrect:
count = [0] * n # Wrong: missing the single-element subsequences
Correct:
count = [1] * n # Each element counts as one subsequence of length 1
Pitfall: Not Handling the Final Answer Accumulation Properly
Some implementations incorrectly return count[n-1]
or max(count)
instead of properly accumulating all counts that correspond to the maximum length.
Incorrect:
return count[n-1] # Wrong: only considers subsequences ending at last position
Correct:
for i in range(n):
# ... DP logic ...
if max_length < dp[i]:
max_length = dp[i]
result = count[i]
elif max_length == dp[i]:
result += count[i] # Accumulate all subsequences with max length
return result
These pitfalls can lead to undercounting the total number of longest increasing subsequences, producing incorrect results even when the algorithm correctly identifies the maximum length.
Which two pointer techniques do you use to check if a string is a palindrome?
Recommended Readings
Segment Tree Faster Range Queries For this article we want to introduce the idea of a Segment Tree Segment Trees allow us to quickly perform range queries as well as range updates Suppose we have an array and we want to know the sum of a particular range of numbers as well
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!