Article Categories
- All Categories
-
Data Structure
-
Networking
-
RDBMS
-
Operating System
-
Java
-
MS Excel
-
iOS
-
HTML
-
CSS
-
Android
-
Python
-
C Programming
-
C++
-
C#
-
MongoDB
-
MySQL
-
Javascript
-
PHP
-
Economics & Finance
Longest Increasing Subsequence in Python
The Longest Increasing Subsequence (LIS) problem asks us to find the length of the longest subsequence in an array where elements are in strictly increasing order. For example, in the array [10,9,2,5,3,7,101,18], the LIS is [2,3,7,101] with length 4.
Algorithm Overview
We'll use a binary search approach with O(n log n) time complexity ?
- Create a
tailsarray to store the smallest tail element for each possible LIS length - For each number, use binary search to find its correct position
- Update the
tailsarray and track the maximum size
Step-by-Step Algorithm
-
tails:= an array of length equal to input array, initialized with zeros -
size:= 0 (tracks current LIS length) - For each element x in the input array:
- Use binary search to find the position where x should be placed
- Update
tails[position] = x - Update
sizeif we found a longer subsequence
- Return the final size
Implementation
class Solution:
def lengthOfLIS(self, nums):
if not nums:
return 0
tails = [0] * len(nums)
size = 0
for x in nums:
# Binary search for the correct position
i, j = 0, size
while i != j:
mid = i + (j - i) // 2
if tails[mid] < x:
i = mid + 1
else:
j = mid
# Place x at position i
tails[i] = x
size = max(i + 1, size)
return size
# Test the solution
solution = Solution()
nums = [10, 9, 2, 5, 3, 7, 101, 18]
result = solution.lengthOfLIS(nums)
print(f"Input: {nums}")
print(f"Length of LIS: {result}")
Input: [10, 9, 2, 5, 3, 7, 101, 18] Length of LIS: 4
How It Works
The tails array maintains the smallest ending element for each possible LIS length. For our example ?
# Let's trace through the algorithm step by step
def lengthOfLIS_with_trace(nums):
tails = [0] * len(nums)
size = 0
print(f"Processing array: {nums}")
for idx, x in enumerate(nums):
i, j = 0, size
while i != j:
mid = i + (j - i) // 2
if tails[mid] < x:
i = mid + 1
else:
j = mid
tails[i] = x
size = max(i + 1, size)
print(f"Step {idx + 1}: x={x}, position={i}, tails={tails[:size]}, size={size}")
return size
# Trace the example
nums = [10, 9, 2, 5, 3, 7, 101, 18]
result = lengthOfLIS_with_trace(nums)
print(f"\nFinal LIS length: {result}")
Processing array: [10, 9, 2, 5, 3, 7, 101, 18] Step 1: x=10, position=0, tails=[10], size=1 Step 2: x=9, position=0, tails=[9], size=1 Step 3: x=2, position=0, tails=[2], size=1 Step 4: x=5, position=1, tails=[2, 5], size=2 Step 5: x=3, position=1, tails=[2, 3], size=2 Step 6: x=7, position=2, tails=[2, 3, 7], size=3 Step 7: x=101, position=3, tails=[2, 3, 7, 101], size=4 Step 8: x=18, position=3, tails=[2, 3, 7, 18], size=4 Final LIS length: 4
Alternative: Dynamic Programming Approach
For comparison, here's the simpler O(n²) dynamic programming solution ?
def lengthOfLIS_dp(nums):
if not nums:
return 0
dp = [1] * len(nums) # dp[i] = length of LIS ending at index i
for i in range(1, len(nums)):
for j in range(i):
if nums[j] < nums[i]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
# Test both approaches
nums = [10, 9, 2, 5, 3, 7, 101, 18]
print(f"Binary Search approach: {Solution().lengthOfLIS(nums)}")
print(f"DP approach: {lengthOfLIS_dp(nums)}")
Binary Search approach: 4 DP approach: 4
Comparison
| Approach | Time Complexity | Space Complexity | Best For |
|---|---|---|---|
| Binary Search | O(n log n) | O(n) | Large datasets |
| Dynamic Programming | O(n²) | O(n) | Small datasets, easier to understand |
Conclusion
The binary search approach efficiently finds the LIS length in O(n log n) time by maintaining a tails array of smallest ending elements. For large datasets, this approach significantly outperforms the O(n²) dynamic programming solution while using the same space complexity.
