Longest Increasing Subsequence in DSA Python - Time & Space Complexity
We want to understand how the time needed to find the longest increasing subsequence changes as the input list grows.
How does the number of steps grow when the list gets bigger?
Analyze the time complexity of the following code snippet.
def length_of_lis(nums):
n = len(nums)
dp = [1] * n
for i in range(n):
for j in range(i):
if nums[j] < nums[i]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp) if dp else 0
This code finds the length of the longest increasing subsequence in a list of numbers using dynamic programming.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Nested loops where for each element, we check all previous elements.
- How many times: Outer loop runs n times, inner loop runs up to i times, so roughly n x n = n² times.
As the list size grows, the number of comparisons grows roughly with the square of the size.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 100 checks |
| 100 | About 10,000 checks |
| 1000 | About 1,000,000 checks |
Pattern observation: Doubling the input size roughly quadruples the work needed.
Time Complexity: O(n²)
This means the time needed grows roughly with the square of the input size, so bigger lists take much more time.
[X] Wrong: "The algorithm runs in linear time because it just loops through the list once."
[OK] Correct: There is a nested loop inside the first loop, so for each element, it checks many previous elements, making it slower than just one loop.
Understanding this time complexity helps you explain why some solutions are slower and shows your skill in analyzing nested loops, a common interview topic.
"What if we used a binary search approach to build the subsequence? How would the time complexity change?"
Practice
Solution
Step 1: Understand the definition of LIS
The LIS is a subsequence where every next number is strictly greater than the previous one.Step 2: Compare options with the definition
Only The longest subsequence where each number is strictly greater than the previous one matches the definition of LIS.Final Answer:
The longest subsequence where each number is strictly greater than the previous one -> Option DQuick Check:
LIS = strictly increasing subsequence [OK]
- Confusing LIS with longest equal subsequence
- Thinking LIS allows decreasing numbers
- Assuming LIS means any order
dp to store LIS lengths for each element in nums of length n?Solution
Step 1: Understand LIS dp initialization
Each element alone is a subsequence of length 1, so dp should start with 1s.Step 2: Check options for correct initialization
Only dp = [1] * n initializes dp with 1s for all elements.Final Answer:
dp = [1] * n -> Option BQuick Check:
dp initialized with 1 for LIS length [OK]
- Initializing dp with zeros instead of ones
- Using None which causes errors in comparisons
- Leaving dp empty
nums = [10, 9, 2, 5, 3, 7, 101, 18]
dp = [1] * len(nums)
for i in range(len(nums)):
for j in range(i):
if nums[j] < nums[i]:
dp[i] = max(dp[i], dp[j] + 1)
print(max(dp))Solution
Step 1: Trace dp updates for each element
Starting dp = [1,1,1,1,1,1,1,1]. Update dp as we find increasing subsequences:
- i=1: nums[1]=9 < 10? Yes, dp[1]=max(1, dp[0]+1)=2.
- i=2: nums[2]=2 < 10,9? No update.
- i=3: nums[3]=5 > 2, dp[3]=max(1, dp[2]+1)=2.
- i=4: nums[4]=3 > 2, dp[4]=2.
- i=5: nums[5]=7 > 5,3,2, dp[5]=max(1, dp[3]+1=3, dp[4]+1=3)=3.
- i=6: nums[6]=101 > all previous, dp[6]=max(1, dp[5]+1=4, dp[1]+1=3, dp[0]+1=2, dp[3]+1=3, dp[4]+1=3) = 5.
- i=7: nums[7]=18 > 7, dp[7]=max(1, dp[5]+1=4, dp[3]+1=3, dp[4]+1=3) = 4.Step 2: Find max dp value
Maximum dp value is 5, which is the length of LIS.Final Answer:
5 -> Option AQuick Check:
Longest Increasing Subsequence length = 5 [OK]
- Forgetting to update dp[i] with max value
- Mixing up indices i and j
- Printing dp instead of max(dp)
def length_of_lis(nums):
dp = [1] * len(nums)
for i in range(len(nums)):
for j in range(i):
if nums[i] > nums[j]:
dp[j] = max(dp[j], dp[i] + 1)
return max(dp)Solution
Step 1: Identify which dp index to update
In LIS, dp[i] stores the length of LIS ending at index i. So dp[i] must be updated when nums[i] > nums[j].Step 2: Check the code's update statement
The code updates dp[j], which is incorrect. It should update dp[i].Final Answer:
dp[i] should be updated, not dp[j] -> Option CQuick Check:
Update dp[i] for LIS ending at i [OK]
- Updating dp[j] instead of dp[i]
- Reversing comparison operators
- Returning min(dp) instead of max(dp)
nums = [3, 10, 2, 1, 20], which of the following is the correct longest increasing subsequence using a binary search optimization approach?Solution
Step 1: Understand binary search LIS approach
This method builds a list 'tails' where each element is the smallest possible tail of an increasing subsequence of given length.Step 2: Trace the tails list for nums
Start with empty tails:
- 3: tails = [3]
- 10: tails = [3, 10]
- 2: replace 3 with 2, tails = [2, 10]
- 1: replace 2 with 1, tails = [1, 10]
- 20: tails = [1, 10, 20]
The LIS length is 3, and one valid LIS is [3, 10, 20].Final Answer:
[3, 10, 20] -> Option AQuick Check:
Binary search LIS = [3, 10, 20] [OK]
- Confusing tails with actual subsequence
- Replacing wrong elements in tails
- Choosing subsequence with wrong order
Ready to prove it?
Pass the test and this pattern moves unknown
