The Longest Increasing Subsequence (LIS) problem is a fundamental concept in dynamic programming that comes up frequently in interviews and competitive programming. Essentially, the goal is to identify the longest subsequence in a given sequence where the elements are sorted in increasing order. It’s a fascinating problem that helps in understanding how to approach complexity and optimize solutions.
Before diving into LIS, let's clarify what a subsequence is. A subsequence is a sequence derived from another sequence by deleting some or no elements without changing the order of the remaining elements. For example, consider the sequence:
A = [3, 10, 2, 1, 20]
Some valid subsequences of A are:
But [3, 1, 10] is not a subsequence since the order is not preserved.
The Longest Increasing Subsequence is one of the longest subsequences that can be formed such that all elements in it are sorted in increasing order. Using the earlier example, the LIS of the sequence A is [3, 10, 20], which has a length of 3.
For a clearer understanding, let’s take the sequence again:
A = [3, 10, 2, 1, 20]
If we start examining potential increasing subsequences:
3
, we can include 10
and 20
.2
, the only larger number after 2
is 20
, leading to [2, 20].1
, the longest subsequence remains [1, 20].Ultimately, the longest subsequences we can form are [3, 10, 20] or [2, 20].
We can tackle the LIS problem in a few ways, but we’ll cover the two most common approaches: the DP approach and the Binary Search + Dynamic Programming approach.
This approach uses a dynamic programming table to store the length of the longest increasing subsequence ending at each index.
Initialization: Create a DP array where each index initially contains the value 1 (since the minimum LIS ending at an element is itself).
Filling the DP table: For every element in the input array:
Here’s the implementation:
def length_of_lis(nums): if not nums: return 0 dp = [1] * len(nums) for i in range(1, len(nums)): for j in range(i): if nums[i] > nums[j]: dp[i] = max(dp[i], dp[j] + 1) return max(dp) # Example A = [3, 10, 2, 1, 20] print(length_of_lis(A)) # Output: 3
The time complexity of this approach is O(n^2), where n is the number of elements in the input array.
This advanced technique helps reduce the time complexity to O(n log n) using a combination of dynamic programming and binary search. The key idea is to maintain an array where we store the smallest tail for all increasing subsequences.
Initialization: Create an empty list tails
. This will store the smallest tail for all increasing subsequences.
Binary Search Logic: Iterate over each number in the list:
tails
that is greater than or equal to the current number.tails
.Here's the implementation:
import bisect def length_of_lis(nums): if not nums: return 0 tails = [] for num in nums: index = bisect.bisect_left(tails, num) if index == len(tails): tails.append(num) else: tails[index] = num return len(tails) # Example A = [3, 10, 2, 1, 20] print(length_of_lis(A)) # Output: 3
Understanding and solving the Longest Increasing Subsequence problem not only showcases your algorithmic prowess but also builds a strong foundation in dynamic programming concepts that will be invaluable in tackling other challenges.
13/10/2024 | DSA
15/11/2024 | DSA
23/09/2024 | DSA
16/11/2024 | DSA
23/09/2024 | DSA
15/11/2024 | DSA
06/12/2024 | DSA
06/12/2024 | DSA
15/11/2024 | DSA
16/11/2024 | DSA
16/11/2024 | DSA
08/12/2024 | DSA