Longest Increasing Subsequence in DSA Go - 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.
func lengthOfLIS(nums []int) int {
n := len(nums)
dp := make([]int, n)
maxLen := 0
for i := 0; i < n; i++ {
dp[i] = 1
for j := 0; j < i; j++ {
if nums[j] < nums[i] && dp[j]+1 > dp[i] {
dp[i] = dp[j] + 1
}
}
if dp[i] > maxLen {
maxLen = dp[i]
}
}
return maxLen
}
This code finds the length of the longest increasing subsequence in an integer slice 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 times n.
As the input size grows, the number of comparisons grows roughly by 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 inner loop runs a fixed number of times, so the algorithm is O(n)."
[OK] Correct: The inner loop depends on the current position i, so it grows with input size, making the total work grow like n squared.
Understanding this time complexity helps you explain why some solutions are slower and shows your grasp of nested loops and dynamic programming patterns.
"What if we used a binary search approach to find the position to update instead of the inner loop? How would the time complexity change?"
Practice
Solution
Step 1: Understand the LIS definition
The LIS is a subsequence (not necessarily contiguous) where every next number is strictly greater than the previous.Step 2: Compare options with definition
The longest subsequence where each number is larger than the previous one matches the LIS definition exactly. Other options describe different concepts.Final Answer:
The longest subsequence where each number is larger than the previous one -> Option CQuick Check:
LIS = strictly increasing subsequence [OK]
- Confusing subsequence with subarray
- Thinking LIS requires contiguous elements
- Mixing increasing with decreasing sequences
nums?Solution
Step 1: Understand slice initialization for fixed length
We need a slice with length equal to input length to store LIS lengths at each index.Step 2: Check each option
lis := make([]int, len(nums)) creates a slice with length len(nums), ready to store values. lis := []int{} creates empty slice with length 0. var lis [len(nums)]int is invalid syntax for arrays with dynamic length. lis := make([]int, 0, len(nums)) creates slice with length 0 but capacity len(nums), so cannot index directly.Final Answer:
lis := make([]int, len(nums)) -> Option DQuick Check:
Slice with length = len(nums) for LIS storage [OK]
- Using empty slice without length
- Trying to declare array with variable length
- Confusing capacity with length
nums := []int{10, 9, 2, 5, 3, 7, 101, 18}?
func lengthOfLIS(nums []int) int {
lis := make([]int, len(nums))
for i := range lis {
lis[i] = 1
}
for i := 1; i < len(nums); i++ {
for j := 0; j < i; j++ {
if nums[i] > nums[j] && lis[i] < lis[j]+1 {
lis[i] = lis[j] + 1
}
}
}
maxLen := 0
for _, length := range lis {
if length > maxLen {
maxLen = length
}
}
return maxLen
}
fmt.Println(lengthOfLIS(nums))Solution
Step 1: Initialize LIS array with 1s
Each element starts with LIS length 1 (itself).Step 2: Update LIS values by checking previous elements
Check pairs (i,j) where nums[i] > nums[j], update lis[i] = max(lis[i], lis[j]+1).Step 3: Calculate max LIS length
For given nums, LIS is [2,3,7,101] length 4.Final Answer:
4 -> Option AQuick Check:
LIS length for example = 4 [OK]
- Confusing subsequence with subarray
- Not updating lis[i] correctly
- Forgetting to initialize lis with 1
func lengthOfLIS(nums []int) int {
lis := make([]int, len(nums))
for i := 0; i < len(nums); i++ {
lis[i] = 0
}
for i := 1; i < len(nums); i++ {
for j := 0; j < i; j++ {
if nums[i] > nums[j] && lis[i] < lis[j] + 1 {
lis[i] = lis[j] + 1
}
}
}
maxLen := 0
for _, length := range lis {
if length > maxLen {
maxLen = length
}
}
return maxLen
}Solution
Step 1: Check lis initialization
lis should start with 1 for each element because each element alone is a subsequence of length 1.Step 2: Effect of zero initialization
Starting with 0 causes maxLen to be zero if no updates happen, giving wrong result.Final Answer:
lis slice is initialized with zeros instead of ones -> Option AQuick Check:
Initialize lis with 1, not 0 [OK]
- Starting lis values at 0
- Wrong loop start index
- Misunderstanding LIS condition
nums := []int{3, 10, 2, 1, 20}, which of the following is the correct longest increasing subsequence returned by a function that reconstructs the LIS (not just its length)?Solution
Step 1: Identify increasing subsequences
Check subsequences where each next number is bigger than previous.Step 2: Find longest valid subsequence
Possible LIS are [3,10,20] and [1,20], but [3,10,20] is longer with length 3.Step 3: Verify options
[3, 10, 20] matches the longest increasing subsequence. [2, 10, 20] and C start with smaller numbers but 2 is after 10 in original, so invalid subsequence. [3, 2, 20] is not increasing.Final Answer:
[3, 10, 20] -> Option BQuick Check:
Longest increasing subsequence = [3, 10, 20] [OK]
- Choosing subsequences not in original order
- Picking non-increasing sequences
- Confusing length with actual subsequence
Ready to prove it?
Pass the test and this pattern moves unknown
