Skip to content

Commit 695f138

Browse files
committed
update DP Longest Common Subsequence
1 parent f02a4e2 commit 695f138

File tree

1 file changed

+213
-27
lines changed

1 file changed

+213
-27
lines changed

✅ Pattern 15: 0-1 Knapsack (Dynamic Programming).md

Lines changed: 213 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -973,14 +973,14 @@ https://leetcode.com/problems/partition-array-into-two-arrays-to-minimize-sum-di
973973
974974
This problem follows the <b>[0/1 Knapsack pattern](#pattern-1-01-knapsack)</b> and can be converted into a <b>[Subset Sum](#🔎-subset-sum-medium)</b> problem.
975975

976-
Let’s assume `S1` and `S2` are the two desired subsets. A basic <b>brute-force</b> solution could be to try adding each element either in `S1` or `S2` in order to find the combination that gives the minimum sum difference between the two sets.
976+
Let’s assume `str1` and `str2` are the two desired subsets. A basic <b>brute-force</b> solution could be to try adding each element either in `str1` or `str2` in order to find the combination that gives the minimum sum difference between the two sets.
977977

978978
So our <b>brute-force</b> algorithm will look like:
979979

980980
```js
981981
for each number 'i'
982-
add number 'i' to S1 and recursively process the remaining numbers
983-
add number 'i' to S2 and recursively process the remaining numbers
982+
add number 'i' to str1 and recursively process the remaining numbers
983+
add number 'i' to str2 and recursively process the remaining numbers
984984
return the minimum absolute difference of the above two sets
985985
```
986986

@@ -1394,27 +1394,27 @@ We are asked to find two subsets of the given numbers whose difference is equal
13941394
(1 + 3) - (1 + 2 ) = 1
13951395
```
13961396

1397-
Now, let’s say `Sum(s1)` denotes the total sum of set `s1`, and `Sum(s2)` denotes the total sum of set `s2`. So the required equation is:
1397+
Now, let’s say `Sum(str1)` denotes the total sum of set `str1`, and `Sum(str2)` denotes the total sum of set `str2`. So the required equation is:
13981398

13991399
```js
1400-
Sum(s1) - Sum(s2) = S
1400+
Sum(str1) - Sum(str2) = S
14011401
```
14021402

14031403
This equation can be reduced to the [subset sum](#🔎-subset-sum-medium) problem. Let’s assume that `Sum(num)` denotes the total sum of all the numbers, therefore:
14041404

14051405
```js
1406-
Sum(s1) + Sum(s2) = Sum(num)
1406+
Sum(str1) + Sum(str2) = Sum(num)
14071407
```
14081408

14091409
Let’s add the above two equations:
14101410

14111411
```js
1412-
=> Sum(s1) - Sum(s2) + Sum(s1) + Sum(s2) = S + Sum(num)
1413-
=> 2 * Sum(s1) = S + Sum(num)
1414-
=> Sum(s1) = (S + Sum(num)) / 2
1412+
=> Sum(str1) - Sum(str2) + Sum(str1) + Sum(str2) = S + Sum(num)
1413+
=> 2 * Sum(str1) = S + Sum(num)
1414+
=> Sum(str1) = (S + Sum(num)) / 2
14151415
```
14161416

1417-
Which means that one of the set `s1` has a sum equal to `(S + Sum(num)) / 2`. This essentially converts our problem to: <b>"Find the count of subsets of the given numbers whose sum is equal to `(S + Sum(num)) / 2`"</b>
1417+
Which means that one of the set `str1` has a sum equal to `(S + Sum(num)) / 2`. This essentially converts our problem to: <b>"Find the count of subsets of the given numbers whose sum is equal to `(S + Sum(num)) / 2`"</b>
14181418

14191419
Let’s take the <b>dynamic programming</b> code of <b>[Count of Subset Sum](#count-of-subset-sum-hard)</b> and extend it to solve this problem:
14201420

@@ -4186,24 +4186,24 @@ console.log(`Minimum palindrome partitions ---> ${findMPPCuts('madam')}`);
41864186
# Pattern 5: Longest Common Substring
41874187
## Longest Common Substring
41884188
https://www.geeksforgeeks.org/longest-common-substring-dp-29/
4189-
> Given two strings `s1` and `s2`, find the length of the longest <b>substring</b> which is common in both the strings.
4189+
> Given two strings `str1` and `str2`, find the length of the longest <b>substring</b> which is common in both the strings.
41904190
41914191
#### Example 1:
41924192
```js
4193-
Input: s1 = "abdca"
4194-
s2 = "cbda"
4193+
Input: str1 = "abdca"
4194+
str2 = "cbda"
41954195
Output: 2
41964196
Explanation: The longest common substring is "bd".
41974197
```
41984198
#### Example 2:
41994199
```js
4200-
Input: s1 = "passport"
4201-
s2 = "ppsspt"
4200+
Input: str1 = "passport"
4201+
str2 = "ppsspt"
42024202
Output: 3
42034203
Explanation: The longest common substring is "ssp".
42044204
```
42054205
### Brute-Force Solution
4206-
A basic <b>brute-force solution</b> could be to try all substrings of `s1` and `s2` to find the longest common one. We can start matching both the strings one character at a time, so we have two options at any step:
4206+
A basic <b>brute-force solution</b> could be to try all substrings of `str1` and `str2` to find the longest common one. We can start matching both the strings one character at a time, so we have two options at any step:
42074207
42084208
1. If the strings have a matching character, we can <i>recursively</i> match for the remaining lengths and keep a track of the current matching length.
42094209
2. If the strings don’t match, we start two new <i>recursive calls</i> by skipping one character separately from each string and reset the matching length.
@@ -4337,19 +4337,21 @@ console.log(
43374337
```
43384338
43394339
### Bottom-up Dynamic Programming
4340-
Since we want to match all the <i>substrings</i> of the given two <i>strings</i>, we can use a two-dimensional array to store our results. The lengths of the two <i>strings</i> will define the size of the two dimensions of the array. So for every index `index1` in string `s1` and `index2` in string `s2`, we have two options:
4340+
Since we want to match all the <i>substrings</i> of the given two <i>strings</i>, we can use a two-dimensional array to store our results. The lengths of the two <i>strings</i> will define the size of the two dimensions of the array. So for every index `index1` in string `str1` and `index2` in string `str2`, we have two options:
43414341
4342-
1. If the character at `s1[index1]` matches `s2[index2]`, the length of the <i>common substring<i> would be one plus the length of the <i>common substring<i> till `index1-1` and `index2-1` indexes in the two <i>strings</i>.
4343-
2. If the character at the `s1[index1]` does not match `s2[index2]`, we don’t have any <i>common substring<i>.
4344-
So our <i>recursive formula<i> would be:
4342+
1. If the character at `str1[index1]` matches `str2[index2]`, the length of the <i>common substring</i> would be one plus the length of the <i>common substring</i> till `index1-1` and `index2-1` indexes in the two <i>strings</i>.
4343+
2. If the character at the `str1[index1]` does not match `str2[index2]`, we don’t have any <i>common substring</i>.
4344+
So our <i>recursive formula</i> would be:
43454345
43464346
```js
4347-
if s1[index1] == s2[index2]
4347+
if str1[index1] == str2[index2]
43484348
dp[index1][index2] = 1 + dp[index1-1][index2-1]
43494349
else
43504350
dp[index1][index2] = 0
43514351
```
4352-
we can clearly see that the <b>longest common substring</b> is of length `2`-- as shown by `dp[3][3]`. Here is the code for our <b>bottom-up dynamic programming approach</b>:
4352+
we can clearly see that the <b>longest common substring</b> is of length `2`-- as shown by `dp[3][3]`.
4353+
4354+
Here is the code for our <b>bottom-up dynamic programming approach</b>:
43534355
```js
43544356
function findLCSLength(str1, str2) {
43554357
const dp = Array(str1.length + 1)
@@ -4388,15 +4390,199 @@ console.log(
43884390
- The <b>time and space complexity</b> of the above algorithm is `O(m∗n)`, where `m` and `n` are the lengths of the two input <i>strings</i>.
43894391
43904392
### Challenge
4391-
Can we further improve our <b>bottom-up DP</b> solution? Can you find an algorithm that has `O(n)` <b>space complexity</b?
4393+
Can we further improve our <b>bottom-up DP</b> solution? Can you find an algorithm that has `O(n)` <b>space complexity</b>
43924394
4395+
## 🔎 Longest Common Subsequence
4396+
https://leetcode.com/problems/longest-common-subsequence/
43934397
4398+
> Given two strings `str1` and `str2`, find the length of the <i>longest subsequence</i> which is common in both the strings.
43944399
4400+
A <b>subsequence</b> is a <i>sequence that can be derived from another sequence by deleting some or no elements without changing the order of the remaining elements</i>.
43954401
4402+
#### Example :
4403+
```js
4404+
Input: str1 = "abdca"
4405+
str2 = "cbda"
4406+
Output: 3
4407+
Explanation: The longest common subsequence is "bda".
4408+
```
4409+
#### Example 2:
4410+
```js
4411+
Input: str1 = "passport"
4412+
str2 = "ppsspt"
4413+
Output: 5
4414+
Explanation: The longest common subsequence is "psspt".
4415+
```
4416+
4417+
### Basic Brute-Force Solution
4418+
A <b>basic brute-force solution</b> could be to try all <i>subsequences</i> of `str1` and `str2` to find the longest one. We can match both the strings one character at a time. So for every index `index1` in `str1` and `index2` in `str2` we must choose between:
4419+
4420+
1. If the character `str1[index1]` matches `str2[index2`, we can <i>recursively</i> match for the remaining lengths.
4421+
2. If the character `str1[index1]` does not match `str2[index2]`, we will start two new <i>recursive calls</i> by skipping one character separately from each string.
4422+
4423+
Here is the code:
4424+
```js
4425+
function findLCSLength(str1, str2) {
4426+
function findLCSLengthRecursive(str1, str2, index1, index2) {
4427+
// base check
4428+
if (index1 === str1.length || index2 === str2.length) return 0;
4429+
4430+
if (str1[index1] === str2[index2])
4431+
return 1 + findLCSLengthRecursive(str1, str2, index1 + 1, index2 + 1);
4432+
4433+
let checkFirstString = findLCSLengthRecursive(
4434+
str1,
4435+
str2,
4436+
index1,
4437+
index2 + 1
4438+
);
4439+
let checkSecondString = findLCSLengthRecursive(
4440+
str1,
4441+
str2,
4442+
index1 + 1,
4443+
index2
4444+
);
4445+
return Math.max(checkFirstString, checkSecondString);
4446+
}
4447+
return findLCSLengthRecursive(str1, str2, 0, 0);
4448+
}
4449+
4450+
console.log(
4451+
`Length of Longest Common Subsequence: ---> ${findLCSLength('abdca', 'cbda')}`
4452+
);
4453+
// Output: 3
4454+
// Explanation: The longest common subsequence is "bda".
4455+
4456+
console.log(
4457+
`Length of Longest Common Subsequence: ---> ${findLCSLength(
4458+
'passport',
4459+
'ppsspt'
4460+
)}`
4461+
);
4462+
// Output: 5
4463+
// Explanation: The longest common subsequence is "psspt".
4464+
```
4465+
4466+
- The <b>time complexity</b> of the above algorithm is exponential `O(2ᵐ⁺ⁿ)`, where `m` and `n` are the lengths of the two input strings.
4467+
- The <b>space complexity</b> is `O(m+n)`, this <b>space</b> will be used to store the <i>recursion stack</i>.
4468+
4469+
### Top-down Dynamic Programming with Memoization
4470+
We can use an array to store the already solved <i>subproblems</i>.
4471+
4472+
The two changing values to our <i>recursive function<i> are the two indexes, `index1` and `index2`. Therefore, we can store the results of all the <i>subproblems</i> in a two-dimensional array. (Another alternative could be to use a <i>hash-table</i> whose key would be a <i>string</i> (`index1` + `|` + `index2`)).
4473+
4474+
Here is the code:
4475+
```js
4476+
function findLCSLength(str1, str2) {
4477+
const dp = [];
4478+
function findLCSLengthRecursive(str1, str2, index1, index2) {
4479+
// base check
4480+
if (index1 === str1.length || index2 === str2.length) return 0;
4481+
4482+
dp[index1] = dp[index1] || [];
4483+
4484+
if (typeof dp[index1][index2] === 'undefined') {
4485+
if (str1[index1] === str2[index2]) {
4486+
dp[index1][index2] =
4487+
1 + findLCSLengthRecursive(str1, str2, index1 + 1, index2 + 1);
4488+
} else {
4489+
let checkFirstString = findLCSLengthRecursive(
4490+
str1,
4491+
str2,
4492+
index1,
4493+
index2 + 1
4494+
);
4495+
let checkSecondString = findLCSLengthRecursive(
4496+
str1,
4497+
str2,
4498+
index1 + 1,
4499+
index2
4500+
);
4501+
dp[index1][index2] = Math.max(checkFirstString, checkSecondString);
4502+
}
4503+
}
4504+
// console.log(dp);
4505+
return dp[index1][index2];
4506+
}
4507+
return findLCSLengthRecursive(str1, str2, 0, 0);
4508+
}
4509+
4510+
console.log(
4511+
`Length of Longest Common Subsequence: ---> ${findLCSLength('abdca', 'cbda')}`
4512+
);
4513+
// Output: 3
4514+
// Explanation: The longest common subsequence is "bda".
4515+
4516+
console.log(
4517+
`Length of Longest Common Subsequence: ---> ${findLCSLength(
4518+
'passport',
4519+
'ppsspt'
4520+
)}`
4521+
);
4522+
// Output: 5
4523+
// Explanation: The longest common subsequence is "psspt".
4524+
```
4525+
#### Bottom-up Dynamic Programming
4526+
Since we want to match all the <b>subsequences</b> of the given two strings, we can use a two-dimensional array to store our results. The lengths of the two strings will define the size of the array’s two dimensions. So for every index `index1` in string `str1` and `index2` in string `str2`, we will choose one of the following two options:
4527+
4528+
1. If the character `str1[index1]` matches `str2[index2]`, the length of the <b>common subsequence</b> would be one plus the length of the <b>common subsequence</b> until the `index1-1` and `index2-1` indexes in the two respective strings.
4529+
2. If the character `str1[index1]`does not match `str2[index2]`, we will take the <i>longest subsequence</i> by either skipping `[index1]th` or `[index2]th` character from the respective strings.
4530+
4531+
So our recursive formula would be:
4532+
```js
4533+
if str1[index1] == str2[index2]
4534+
dp[index1][index2] = 1 + dp[index1-1][index2-1]
4535+
else
4536+
dp[index1][index2] = Math.max(dp[index1-1][index2], dp[index1][index2-1])
4537+
```
4538+
4539+
From the above visualization, we can clearly see that the <b>longest common subsequence</b> is of length `3` – as shown by `dp[4][5]`.
4540+
4541+
Here is the code for our <b>bottom-up dynamic programming approach</b>:
4542+
```js
4543+
function findLCSLength(str1, str2) {
4544+
const dp = Array(str1.length + 1)
4545+
.fill(0)
4546+
.map(() => Array(str2.length + 1)
4547+
.fill(0));
4548+
4549+
let maxLength = 0;
4550+
4551+
for (let index1 = 1; index1 <= str1.length; index1++) {
4552+
for (let index2 = 1; index2 <= str2.length; index2++) {
4553+
if (str1[index1 - 1] === str2[index2 - 1]) {
4554+
dp[index1][index2] = 1 + dp[index1 - 1][index2 - 1];
4555+
} else {
4556+
dp[index1][index2] = Math.max(
4557+
dp[index1 - 1][index2],
4558+
dp[index1][index2 - 1]
4559+
);
4560+
}
4561+
// console.log(dp)
4562+
maxLength = Math.max(maxLength, dp[index1][index2]);
4563+
}
4564+
}
4565+
4566+
return maxLength;
4567+
}
4568+
4569+
console.log(
4570+
`Length of Longest Common Subsequence: ---> ${findLCSLength('abdca', 'cbda')}`);
4571+
// Output: 3
4572+
// Explanation: The longest common subsequence is "bda".
4573+
4574+
console.log(
4575+
`Length of Longest Common Subsequence: ---> ${findLCSLength('passport', 'ppsspt')}`);
4576+
// Output: 5
4577+
// Explanation: The longest common subsequence is "psspt".
4578+
```
4579+
4580+
- The <b>time and space complexity</b> of the above algorithm is `O(m*n)`, where `m` and `n` are the lengths of the two input strings.
4581+
4582+
### Challenge
4583+
Can we further improve our <b>bottom-up DP solution</b>? Can you find an algorithm that has `O(n)` <b>space complexity</b>?
43964584
43974585
4398-
## 😕 🔎 Longest Common Subsequence
4399-
https://leetcode.com/problems/longest-common-subsequence/
44004586
44014587
## Minimum Deletions & Insertions to Transform a String into another
44024588
https://leetcode.com/problems/edit-distance/
@@ -4425,8 +4611,8 @@ https://www.geeksforgeeks.org/longest-bitonic-subsequence-dp-15/
44254611
## Longest Alternating Subsequence
44264612
https://www.geeksforgeeks.org/longest-alternating-subsequence/
44274613
4428-
## Edit Distance
4614+
## 🔎 Edit Distance
44294615
https://leetcode.com/problems/edit-distance/
44304616
4431-
## Strings Interleaving
4617+
## 🔎 Strings Interleaving
44324618
https://leetcode.com/problems/interleaving-string/

0 commit comments

Comments
 (0)