Skip to content

Commit f02a4e2

Browse files
committed
update DP Longest Common Substring
1 parent 3f73ccd commit f02a4e2

File tree

1 file changed

+210
-2
lines changed

1 file changed

+210
-2
lines changed

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

Lines changed: 210 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4027,7 +4027,7 @@ console.log(`Minimum palindrome partitions ---> ${findMPPCuts('pp')}`);
40274027
```
40284028
- The <b>time complexity</b> of the above algorithm is exponential `O(2ⁿ)`, where `n` represents the total number.
40294029
- The <b>space complexity</b> is `O(n)`, which will be used to store the <i>recursion stack</i>.
4030-
### Top-down Dynamic Programming with Memoization#
4030+
### Top-down Dynamic Programming with Memoization
40314031
We can memoize both functions `findMPPCutsRecursive()` and `isPalindrome()`. The two changing values in both these functions are the two indexes; therefore, we can store the results of all the <i>subproblems</i> in a two-dimensional array. (alternatively, we can use a <i>hash-table</i>).
40324032
40334033
Here is the code:
@@ -4099,7 +4099,7 @@ console.log(`Minimum palindrome partitions ---> ${findMPPCuts('pp')}`);
40994099
// Output: 0
41004100
// Explanation: We do not need to cut, as "pp" is a palindrome.
41014101
```
4102-
### Bottom-up Dynamic Programming#
4102+
### Bottom-up Dynamic Programming
41034103
The above solution tells us that we need to build two tables, one for the `isPalindrome()` and one for `findMPPCuts()`.
41044104
41054105
If you remember, we built a table in the [Longest Palindromic Substring (LPS)](#longest-palindromic-subsequence) chapter that can tell us what <i>substrings</i> (of the input <i>string</i>) are <i>palindrome</i>. We will use the same approach here to build the table required for `isPalindrome()`.
@@ -4186,6 +4186,214 @@ 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.
4190+
4191+
#### Example 1:
4192+
```js
4193+
Input: s1 = "abdca"
4194+
s2 = "cbda"
4195+
Output: 2
4196+
Explanation: The longest common substring is "bd".
4197+
```
4198+
#### Example 2:
4199+
```js
4200+
Input: s1 = "passport"
4201+
s2 = "ppsspt"
4202+
Output: 3
4203+
Explanation: The longest common substring is "ssp".
4204+
```
4205+
### 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:
4207+
4208+
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.
4209+
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.
4210+
4211+
The length of the <b>Longest Common Substring (LCS)</b> will be the maximum number returned by the three <i>recurse calls</i> in the above two options.
4212+
4213+
Here is the code:
4214+
```js
4215+
function findLCSLength(str1, str2) {
4216+
function findLCSLengthRecursive(str1, str2, index1, index2, count) {
4217+
//base case
4218+
if (index1 === str1.length || index2 === str2.length) return count;
4219+
4220+
if (str1[index1] === str2[index2]) {
4221+
count = findLCSLengthRecursive(
4222+
str1,
4223+
str2,
4224+
index1 + 1,
4225+
index2 + 1,
4226+
count + 1
4227+
);
4228+
}
4229+
4230+
let checkFirstString = findLCSLengthRecursive(
4231+
str1,
4232+
str2,
4233+
index1,
4234+
index2 + 1,
4235+
0
4236+
);
4237+
let checkSecondString = findLCSLengthRecursive(
4238+
str1,
4239+
str2,
4240+
index1 + 1,
4241+
index2,
4242+
0
4243+
);
4244+
4245+
return Math.max(count, Math.max(checkFirstString, checkSecondString));
4246+
}
4247+
return findLCSLengthRecursive(str1, str2, 0, 0, 0);
4248+
}
4249+
4250+
console.log(
4251+
`Length of Longest Common Substring: ---> ${findLCSLength('abdca', 'cbda')}`
4252+
);
4253+
// Output: 2
4254+
// Explanation: The longest common substring is "bd".
4255+
4256+
console.log(
4257+
`Length of Longest Common Substring: ---> ${findLCSLength(
4258+
'passport',
4259+
'ppsspt'
4260+
)}`
4261+
);
4262+
// Output: 3
4263+
// Explanation: The longest common substring is "ssp".
4264+
```
4265+
4266+
- Because of the three <i>recursive calls</i>, the <b>time complexity</b> of the above algorithm is exponential `O(3ᵐ⁺ⁿ)`, where `m` and `n` are the lengths of the two input strings. The <b>space complexity</b> is `O(m+n)`, this <b>space</b> will be used to store the <i>recursion stack</i>.
4267+
4268+
### Top-down Dynamic Programming with Memoization
4269+
We can use an array to store the already solved <i>subproblems</i>.
4270+
4271+
The three changing values to our <i>recursive function</i> are the two indexes (`index1` and `index2`) and the `count`. Therefore, we can store the results of all <i>subproblems</i>in a <i>three-dimensional array</i>. (Another alternative could be to use a <i>hash-table</i> whose key would be a string (`index1` + `|` `index2` + `|` + `count`)).
4272+
4273+
Here is the code:
4274+
```js
4275+
function findLCSLength(str1, str2) {
4276+
const maxLength = Math.min(str1.length, str2.length);
4277+
const dp = [];
4278+
4279+
function findLCSLengthRecursive(str1, str2, index1, index2, count) {
4280+
//base case
4281+
if (index1 === str1.length || index2 === str2.length) return count;
4282+
4283+
dp[index1] = dp[index1] || [];
4284+
dp[index1][index2] = dp[index1][index2] || [];
4285+
4286+
if (typeof dp[index1][index2][count] === 'undefined') {
4287+
let checkFirstString = count;
4288+
if (str1[index1] === str2[index2]) {
4289+
checkFirstString = findLCSLengthRecursive(
4290+
str1,
4291+
str2,
4292+
index1 + 1,
4293+
index2 + 1,
4294+
count + 1
4295+
);
4296+
}
4297+
4298+
let checkSecondString = findLCSLengthRecursive(
4299+
str1,
4300+
str2,
4301+
index1,
4302+
index2 + 1,
4303+
0
4304+
);
4305+
let checkThirdString = findLCSLengthRecursive(
4306+
str1,
4307+
str2,
4308+
index1 + 1,
4309+
index2,
4310+
0
4311+
);
4312+
dp[index1][index2][count] = Math.max(
4313+
checkFirstString,
4314+
Math.max(checkSecondString, checkThirdString)
4315+
);
4316+
}
4317+
4318+
return dp[index1][index2][count];
4319+
}
4320+
return findLCSLengthRecursive(str1, str2, 0, 0, 0);
4321+
}
4322+
4323+
console.log(
4324+
`Length of Longest Common Substring: ---> ${findLCSLength('abdca', 'cbda')}`
4325+
);
4326+
// Output: 2
4327+
// Explanation: The longest common substring is "bd".
4328+
4329+
console.log(
4330+
`Length of Longest Common Substring: ---> ${findLCSLength(
4331+
'passport',
4332+
'ppsspt'
4333+
)}`
4334+
);
4335+
// Output: 3
4336+
// Explanation: The longest common substring is "ssp".
4337+
```
4338+
4339+
### 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:
4341+
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:
4345+
4346+
```js
4347+
if s1[index1] == s2[index2]
4348+
dp[index1][index2] = 1 + dp[index1-1][index2-1]
4349+
else
4350+
dp[index1][index2] = 0
4351+
```
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>:
4353+
```js
4354+
function findLCSLength(str1, str2) {
4355+
const dp = Array(str1.length + 1)
4356+
.fill(0)
4357+
.map(() => Array(str2.length + 1).fill(0));
4358+
let maxLength = 0;
4359+
4360+
for (let i = 1; i <= str1.length; i++) {
4361+
for (let j = 1; j <= str2.length; j++) {
4362+
if (str1.charAt(i - 1) === str2.charAt(j - 1)) {
4363+
dp[i][j] = 1 + dp[i - 1][j - 1];
4364+
maxLength = Math.max(maxLength, dp[i][j]);
4365+
}
4366+
}
4367+
}
4368+
4369+
return maxLength;
4370+
}
4371+
4372+
console.log(
4373+
`Length of Longest Common Substring: ---> ${findLCSLength('abdca', 'cbda')}`
4374+
);
4375+
// Output: 2
4376+
// Explanation: The longest common substring is "bd".
4377+
4378+
console.log(
4379+
`Length of Longest Common Substring: ---> ${findLCSLength(
4380+
'passport',
4381+
'ppsspt'
4382+
)}`
4383+
);
4384+
// Output: 3
4385+
// Explanation: The longest common substring is "ssp".
4386+
```
4387+
4388+
- 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>.
4389+
4390+
### 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?
4392+
4393+
4394+
4395+
4396+
41894397
41904398
## 😕 🔎 Longest Common Subsequence
41914399
https://leetcode.com/problems/longest-common-subsequence/

0 commit comments

Comments
 (0)