💡 The longest increasing subsequence length is 4, and there are 2 such subsequences.
class FenwickTree:
def __init__(self, size):
self.size = size
self.tree = [(0, 0) for _ in range(size + 1)] # STEP 2
def update(self, i, length, count):
while i <= self.size:
cur_len, cur_count = self.tree[i]
if length > cur_len:
self.tree[i] = (length, count) # STEP 4,6,8,10,12
elif length == cur_len:
self.tree[i] = (cur_len, cur_count + count) # STEP 10
i += i & (-i)
def query(self, i):
max_len, total_count = 0, 0
while i > 0:
cur_len, cur_count = self.tree[i]
if cur_len > max_len:
max_len, total_count = cur_len, cur_count # STEP 3,5,7,9,11,13
elif cur_len == max_len:
total_count += cur_count
i -= i & (-i)
return max_len, total_count
def findNumberOfLIS(nums):
sorted_unique = sorted(set(nums)) # STEP 1
def get_index(x):
import bisect
return bisect.bisect_left(sorted_unique, x) + 1
fenw = FenwickTree(len(sorted_unique)) # STEP 2
for num in nums:
idx = get_index(num) - 1
length, count = fenw.query(idx) # STEP 3,5,7,9,11
fenw.update(get_index(num), length + 1, max(count,1)) # STEP 4,6,8,10,12
max_len, total_count = fenw.query(len(sorted_unique)) # STEP 13
return total_count
if __name__ == '__main__':
print(findNumberOfLIS([1,3,5,4,7])) # Output: 2
📊
Number of Longest Increasing Subsequences - Watch the Algorithm Execute, Step by Step
Watching each Fenwick tree query and update reveals how the algorithm efficiently accumulates counts of longest increasing subsequences without enumerating them explicitly.
Step 1/13
·Active fill★Answer cell
Initializing dp array
i\w
0
1
2
3
i=0
(0,0)
(0,0)
(0,0)
(0,0)
Uninitialized Fenwick tree
Initializing dp array
i\w
0
1
2
3
i=0
(0,0)
(0,0)
(0,0)
(0,0)
Fenwick tree initialized
Item 0 - wt:1 val:1
i\w
0
1
2
3
i=0
(0,0)
(0,0)
(0,0)
(0,0)
Query index 0
Item 0 - wt:1 val:1
i\w
0
1
2
3
i=0
(1,1)
(0,0)
(0,0)
(0,0)
Update index 1
Item 1 - wt:3 val:3
i\w
0
1
2
3
i=0
(1,1)
(0,0)
(0,0)
(0,0)
Query index 1
Item 1 - wt:3 val:3
i\w
0
1
2
3
i=0
(1,1)
(2,1)
(0,0)
(2,1)
Update index 2
Item 2 - wt:5 val:5
i\w
0
1
2
3
i=0
(1,1)
(2,1)
(0,0)
(2,1)
Query index 2
Item 2 - wt:5 val:5
i\w
0
1
2
3
i=0
(1,1)
(2,1)
(3,1)
(3,1)
Update index 3
Item 3 - wt:4 val:4
i\w
0
1
2
3
i=0
(1,1)
(2,1)
(3,1)
(3,1)
Query index 2
Item 3 - wt:4 val:4
i\w
0
1
2
3
i=0
(1,1)
(2,1)
(3,2)
(3,2)
Update index 3 with merged counts
Item 4 - wt:7 val:7
i\w
0
1
2
3
i=0
(1,1)
(2,1)
(3,2)
(3,2)
Query index 3
Item 4 - wt:7 val:7
i\w
0
1
2
3
i=0
(1,1)
(2,1)
(3,2)
(4,2)
Update index 4
Initializing dp array
i\w
0
1
2
3
i=0
(1,1)
(2,1)
(3,2)
(4,2)
Answer cell
Key Takeaways
✓ Fenwick tree efficiently accumulates counts of longest increasing subsequences by storing (length, count) pairs at compressed indices.
This insight is hard to see from code alone because the Fenwick tree's internal state and propagation are abstract; visualization reveals how counts merge.
✓ When updating Fenwick tree, if a new subsequence length equals the current stored length, counts are added rather than replaced.
This merging of counts explains why multiple LIS can be counted without enumerating all subsequences explicitly.
✓ Querying Fenwick tree at index one less than current number's compressed index returns the best LIS info for all smaller numbers.
This shows how the algorithm builds on previously computed results, a key dynamic programming principle visualized step-by-step.
Practice
(1/5)
1. Consider the following Python code implementing the optimized LIS algorithm. What is the value of the variable tails after processing the input [3, 1, 2]?
tails always sorted and minimal last elements [OK]
Hint: tails stores smallest possible ends of subsequences [OK]
Common Mistakes:
Appending instead of replacing in tails
Misunderstanding bisect_left index
Confusing tails with actual subsequence
2. The following code attempts to count arithmetic subsequences but contains a subtle bug. Identify the line causing incorrect counting of subsequences.
medium
A. dp = [defaultdict(int) for _ in range(n)]
B. count_at_j = dp[j][diff]
C. dp[i][diff] += count_at_j + 1
D. total += dp[i][diff] # adding wrong value to total
Solution
Step 1: Understand counting logic
Total should only increase by count_at_j, which counts subsequences extended by nums[i]. Adding dp[i][diff] counts pairs and subsequences multiple times.
Step 2: Identify incorrect line
Line adding dp[i][diff] to total causes double counting; it should add only count_at_j.
Final Answer:
Option D -> Option D
Quick Check:
Correct counting adds count_at_j, not dp[i][diff] [OK]
Hint: Only add count_at_j to total, not dp[i][diff] [OK]
Common Mistakes:
Adding dp[i][diff] to total
Not initializing dp correctly
Counting pairs as subsequences
3. What is the time complexity of the optimal bottom-up DFS solution for computing the diameter of a binary tree with n nodes?
medium
A. O(n) because each node is visited once in the DFS traversal
B. O(n^2) due to repeated height computations in recursion
C. O(n log n) because height computations take log n time per node
D. O(n) but with additional O(n) space for memoization arrays
In naive recursion without memoization, height computations are repeated, causing O(n^2) time complexity.
Step 2: Confirm that optimal bottom-up DFS avoids repeated computations
The question asks for the optimal bottom-up DFS solution, which is O(n), but the options are designed to test understanding of naive vs optimal.
Step 3: Since O(n) because each node is visited once in the DFS traversal states optimal O(n), but O(n^2) due to repeated height computations in recursion states O(n^2) due to repeated computations, and question asks for optimal solution, correct answer is A.
Final Answer:
Option A -> Option A
Quick Check:
Single DFS traversal over n nodes -> O(n) time [OK]
Hint: Single DFS traversal means linear time [OK]
Common Mistakes:
Confusing with brute force O(n^2)
Assuming height calls cost log n each
4. What is the time complexity of the topological sort based DP solution for the longest increasing path in an m x n matrix?
medium
A. O(m * n * log(m * n))
B. O(m * n^2)
C. O(m * n * 4^{m*n})
D. O(m * n)
Solution
Step 1: Identify sorting cost
Sorting all m*n cells by their values costs O(m*n*log(m*n)).
Step 2: Analyze DP updates
Each cell checks up to 4 neighbors, so DP updates cost O(m*n) total.
Final Answer:
Option A -> Option A
Quick Check:
Sorting dominates, so complexity is O(m*n*log(m*n)) [OK]
Hint: Sorting cells dominates complexity [OK]
Common Mistakes:
Assuming DP is O(m*n) ignoring sorting
Confusing exponential brute force with DP
Ignoring log factor from sorting
5. What is the time complexity of the bottom-up bitmask DP solution for the Travelling Salesman Problem with n cities, and why?
Options:
medium
A. O(n * 2^n) because we process each subset and city once
B. O(2^n) because subsets dominate the complexity
C. O(n^2 * 2^n) because for each subset and city, we check all possible previous cities
D. O(n! ) because we consider all permutations of cities
Solution
Step 1: Identify loops in the code
There are three nested loops: over all subsets (2^n), over all cities (n), and over all possible previous cities (n).
Step 2: Multiply loop complexities
Total complexity is O(2^n * n * n) = O(n^2 * 2^n). O(n * 2^n) because we process each subset and city once misses the inner loop over previous cities, underestimating complexity.
Final Answer:
Option C -> Option C
Quick Check:
Bitmask DP complexity is well-known as O(n^2 * 2^n) [OK]
Hint: Remember triple nested loops: subsets, current city, previous city [OK]
Common Mistakes:
Ignoring inner loop over previous cities leads to underestimation
Recommended
Ready to prove it?
Pass the test and this pattern moves unknownstrongwhile earning +1 Grip toward your certificate.