π Why This Pattern?
Many problems revolve around choosing or skipping elements in a sequence to form valid subsequences. The challenge comes from maintaining order and finding optimal properties (length, count, sum, lexicographic order, etc.).
Dynamic Programming shines here because:
- Overlapping subproblems exist (checking subsequences at different indexes).
- Optimal substructure appears (solution to prefix depends on smaller prefixes).
π Core Ideas
-
Define
dp[i]
ordp[i][j]
where state represents:-
i
β position in first string/array. -
j
β position in second string (if comparing).
-
-
Transitions:
- Include current element.
- Exclude current element.
Carefully manage base cases.
π Problems & Solutions
1οΈβ£ Longest Increasing Subsequence (LIS)
Find the longest subsequence where elements are strictly increasing.
π Key Intuition:
For each element, check all previous ones smaller than it, then extend their LIS length.
public class LIS {
public static int lengthOfLIS(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
int maxLen = 1;
for (int i = 0; i < n; i++) {
dp[i] = 1; // every element is LIS of length 1
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
maxLen = Math.max(maxLen, dp[i]);
}
return maxLen;
}
public static void main(String[] args) {
int[] nums = {10, 9, 2, 5, 3, 7, 101, 18};
System.out.println(lengthOfLIS(nums)); // 4 (2,3,7,101)
}
}
πΉ Variations:
- Count number of LIS.
- Print one actual LIS.
- Apply patience sorting for
O(n log n)
version.
2οΈβ£ Longest Common Subsequence (LCS)
Given two strings, find the length of the longest subsequence that appears in both.
π Key Intuition:
At each step, either characters match β include in LCS, else β take max of excluding one.
public class LCS {
public static int lcs(String a, String b) {
int n = a.length(), m = b.length();
int[][] dp = new int[n + 1][m + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (a.charAt(i - 1) == b.charAt(j - 1)) {
dp[i][j] = 1 + dp[i - 1][j - 1];
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[n][m];
}
public static void main(String[] args) {
System.out.println(lcs("abcde", "ace")); // 3 (ace)
}
}
πΉ Variations:
- Print actual LCS.
- Shortest Common Supersequence.
- Min Deletions to make strings equal.
3οΈβ£ Longest Palindromic Subsequence
Find the longest subsequence of a string that reads the same forward & backward.
π Key Intuition:
If chars match β expand both ends. Else β try removing one side.
public class LPS {
public static int longestPalindromicSubsequence(String s) {
int n = s.length();
int[][] dp = new int[n][n];
for (int i = n - 1; i >= 0; i--) {
dp[i][i] = 1;
for (int j = i + 1; j < n; j++) {
if (s.charAt(i) == s.charAt(j)) {
dp[i][j] = 2 + dp[i + 1][j - 1];
} else {
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
}
}
}
return dp[0][n - 1];
}
public static void main(String[] args) {
System.out.println(longestPalindromicSubsequence("bbbab")); // 4 ("bbbb")
}
}
4οΈβ£ Distinct Subsequences Count
Count number of distinct subsequences of s
that equal t
.
π Key Intuition:
At each step, either use matching character or skip it.
public class DistinctSubsequences {
public static int numDistinct(String s, String t) {
int n = s.length(), m = t.length();
int[][] dp = new int[n + 1][m + 1];
for (int i = 0; i <= n; i++) dp[i][0] = 1; // empty t always possible
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s.charAt(i - 1) == t.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n][m];
}
public static void main(String[] args) {
System.out.println(numDistinct("rabbbit", "rabbit")); // 3
}
}
5οΈβ£ Partition Problems (Subsequence Sum)
Check if array can be partitioned into two subsets with equal sum.
π Key Intuition:
Reduce to subset-sum problem.
public class PartitionEqualSubsetSum {
public static boolean canPartition(int[] nums) {
int sum = 0;
for (int num : nums) sum += num;
if (sum % 2 != 0) return false;
int target = sum / 2;
boolean[] dp = new boolean[target + 1];
dp[0] = true;
for (int num : nums) {
for (int j = target; j >= num; j--) {
dp[j] = dp[j] || dp[j - num];
}
}
return dp[target];
}
public static void main(String[] args) {
int[] nums = {1, 5, 11, 5};
System.out.println(canPartition(nums)); // true
}
}
π₯ More Problems You Should Practice
- Minimum Deletions to Make Palindrome
- Count Palindromic Subsequences
- Shortest Palindrome Insertions
- Max Sum Increasing Subsequence
- Word Break (subsequence + dictionary check)
π Key Takeaways
- Subsequence = skip or include β classic DP choice.
- States often track index positions and transitions come from choice-making.
- Problems differ in what you optimize: length, count, sum, or structure.
Top comments (0)