Skip to content

Commit 0058afa

Browse files
committed
update DP longest palindromic substring
1 parent 76468ea commit 0058afa

File tree

1 file changed

+205
-10
lines changed

1 file changed

+205
-10
lines changed

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

Lines changed: 205 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3228,7 +3228,7 @@ console.log(`Maximum stealing: ---> ${findMaxSteal([2, 10, 14, 8, 1])}`);
32283228
```
32293229
- The above solution has <b>time and space complexity</b> of `O(n)`.
32303230
#### Memory optimization
3231-
We can optimize the space used in our previous solution. We don’t need to store all the previous numbers up to `n`, as we only need two previous numbers to calculate the next number in the sequence. Let’s use this fact to further improve our solution:
3231+
We can optimize the space used in our previous solution. We don’t need to store all the previous numbers up to `n`, as we only need two previous numbers to calculate the next number in the <b>sequence</b>. Let’s use this fact to further improve our solution:
32323232
32333233
```js
32343234
function findMaxSteal(wealth) {
@@ -3256,24 +3256,24 @@ We can clearly see that this problem follows the <b>[Fibonacci number pattern](#
32563256
# Pattern 4: Palindromic Subsequence
32573257
## Longest Palindromic Subsequence
32583258
https://leetcode.com/problems/longest-palindromic-subsequence/
3259-
> Given a sequence, find the length of its <b>Longest Palindromic Subsequence (LPS)</b>. In a <b>palindromic subsequence</b>, <i>elements read the same backward and forward</i>.
3259+
> Given a <b>sequence</b>, find the length of its <b>Longest Palindromic Subsequence (LPS)</b>. In a <b>palindromic subsequence</b>, <i>elements read the same backward and forward</i>.
32603260
32613261
A <b>subsequence</b> is a <i>sequence</i> that can be derived from another <i>sequence</i> by <i>deleting some or no elements without changing the order of the remaining elements</i>.
32623262
32633263
#### Example 1:
3264-
```
3264+
```js
32653265
Input: "abdbca"
32663266
Output: 5
32673267
Explanation: LPS is "abdba".
32683268
```
32693269
#### Example 2:
3270-
```
3270+
```js
32713271
Input: = "cddpd"
32723272
Output: 3
32733273
Explanation: LPS is "ddd".
32743274
```
32753275
#### Example 3:
3276-
```
3276+
```js
32773277
Input: = "pqr"
32783278
Output: 1
32793279
Explanation: LPS could be "p", "q" or "r".
@@ -3322,7 +3322,7 @@ findLPSLength('pqr');
33223322
// Output: 1
33233323
// Explanation: LPS could be "p", "q" or "r".
33243324
```
3325-
- In each function call, we are either having one <i>recursive call</i> or <i>two recursive calls</i> (we will never have <i>three recursive calls</i>); hence, the <b>time complexity</b> of the above algorithm is exponential `O(2ⁿ)`, where `n` is the length of the input sequence. The <b>space complexity</b> is `O(n)`, which is used to store the <i>recursion stack</i>.
3325+
- In each function call, we are either having one <i>recursive call</i> or <i>two recursive calls</i> (we will never have <i>three recursive calls</i>); hence, the <b>time complexity</b> of the above algorithm is exponential `O(2ⁿ)`, where `n` is the length of the input <i>sequence</i>. The <b>space complexity</b> is `O(n)`, which is used to store the <i>recursion stack</i>.
33263326
33273327
### Top-down Dynamic Programming with Memoization
33283328
We can use an array to store the already solved <i>subproblems</i>.
@@ -3372,19 +3372,20 @@ findLPSLength('pqr');
33723372
// Output: 1
33733373
// Explanation: LPS could be "p", "q" or "r".
33743374
```
3375-
- Since our memoization array `dp[str.length][str.length]` stores the results for all the <i>subproblems</i>, we can conclude that we will not have more than `N*N` <i>subproblems</i>(where `N` is the length of the input sequence). This means that our time complexity will be `O(N²)`.
3375+
- Since our <b>memoization</b> array `dp[str.length][str.length]` stores the results for all the <i>subproblems</i>, we can conclude that we will not have more than `N*N` <i>subproblems</i>(where `N` is the length of the input <i>sequence</i>). This means that our time complexity will be `O(N²)`.
33763376
- The above algorithm will be using `O(N²)` <b>space</b> for the <b>memoization</b> array. Other than that we will use `O(N)` <b>space</b> for the <i>recursion call-stack</i>. So the total <b>space complexity</b> will be `O(N² + N)`, which is asymptotically equivalent to `O(N²)`.
33773377
33783378
### Bottom-up Dynamic Programming
3379-
Since we want to try all the subsequences of the given sequence, we can use a two-dimensional array to store our results. We can start from the beginning of the sequence and keep adding one element at a time. At every step, we will try all of its subsequences. So for every `startIndex` and `endIndex` in the given string, we will choose one of the following two options:
3379+
Since we want to try all the <b>subsequences</b> of the given <i>sequence</i>, we can use a two-dimensional array to store our results. We can start from the beginning of the <i>sequence</i> and keep adding one element at a time. At every step, we will try all of its <b>subsequences</b>. So for every `startIndex` and `endIndex` in the given string, we will choose one of the following two options:
33803380
3381-
1. If the element at the `startIndex` matches the element at the `endIndex`, the length of <b>LPS</b> would be two plus the length of <b>LPS</b> till `startIndex+1`` and endIndex-1`.
3381+
1. If the element at the `startIndex` matches the element at the `endIndex`, the length of <b>LPS</b> would be two plus the length of <b>LPS</b> until `startIndex+1` and `endIndex-1`.
33823382
2. If the element at the `startIndex` does not match the element at the `endIndex`, we will take the maximum <b>LPS</b> created by either skipping element at the `startIndex` or the `endIndex`.
33833383
33843384
So our recursive formula would be:
33853385
```js
33863386
if st[endIndex] == st[startIndex]
33873387
dp[startIndex][endIndex] = 2 + dp[startIndex + 1][endIndex - 1]
3388+
33883389
else
33893390
dp[startIndex][endIndex] = Math.max(dp[startIndex + 1][endIndex], dp[startInde[endIndex - 1])
33903391
```
@@ -3432,10 +3433,204 @@ console.log('Length of LPS ---> ' + findLPSLength('pqr'));
34323433
// Output: 1
34333434
// Explanation: LPS could be "p", "q" or "r".
34343435
```
3435-
- The <b>time and space complexity</b> of the above algorithm is `O(n²)`, where `n` is the length of the input sequence.
3436+
- The <b>time and space complexity</b> of the above algorithm is `O(n²)`, where `n` is the length of the input <i>sequence</i>.
34363437
## 👩🏽‍🦯 Longest Palindromic Substring
34373438
https://leetcode.com/problems/longest-palindromic-substring/
34383439
3440+
> Given a string, find the length of its <b>Longest Palindromic Substring (LPS)</b>. In a <i>palindromic</i> string, <i>elements read the same backward and forward</i>.
3441+
3442+
#### Example 1:
3443+
```
3444+
Input: "abdbca"
3445+
Output: 3
3446+
Explanation: LPS is "bdb".
3447+
```
3448+
#### Example 2:
3449+
```
3450+
Input: = "cddpd"
3451+
Output: 3
3452+
Explanation: LPS is "dpd".
3453+
```
3454+
#### Example 3:
3455+
```
3456+
Input: = "pqr"
3457+
Output: 1
3458+
Explanation: LPS could be "p", "q" or "r".
3459+
```
3460+
### Basic Brute-Force Solution
3461+
This problem follows the <b>[Longest Palindromic Subsequence pattern](#pattern-4-palindromic-subsequence)</b>. The only difference is that in a <i>palindromic subsequence</i> characters can be non-adjacent, whereas in a <i>substring</i> all characters should form a <i>palindrome</i>. We will follow a similar approach though.
3462+
3463+
The <b>basic brute-force solution</b> will be to try all the <i>substrings</i> of the given string. We can start processing from the beginning and the end of the string. So at any step, we will have two options:
3464+
3465+
1. If the element at the beginning and the end are the same, we make a <i>recursive call</i> to check if the remaining <i>substring</i> is also a <i>palindrome</i>. If so, the <i>substring</i> is a <i>palindrome</i> from beginning to end.
3466+
2. We will skip either the element from the beginning or the end to make two <i>recursive calls</i> for the remaining <i>substring</i>. The length of <b>LPS</b> would be the maximum of these two <i>recursive calls</i>.
3467+
3468+
Here is the code:
3469+
```js
3470+
function findLPSLength(str) {
3471+
function findLPSLengthRecursive(str, startIndex, endIndex) {
3472+
//base check
3473+
if (startIndex > endIndex) return 0;
3474+
3475+
//every string with one character is a palindrome
3476+
if (startIndex === endIndex) return 1;
3477+
3478+
//case 1: elements at the start and the end are the same
3479+
if (str[startIndex] === str[endIndex]) {
3480+
const remainingLength = endIndex - startIndex - 1;
3481+
3482+
//check if the remaining string is also a palindrom
3483+
if (
3484+
remainingLength ===
3485+
findLPSLengthRecursive(str, startIndex + 1, endIndex - 1)
3486+
) {
3487+
return remainingLength + 2;
3488+
}
3489+
}
3490+
3491+
//case 2: skip one character either from the start or end
3492+
const skipStartChar = findLPSLengthRecursive(str, startIndex + 1, endIndex);
3493+
const skipEndChar = findLPSLengthRecursive(str, startIndex, endIndex - 1);
3494+
return Math.max(skipStartChar, skipEndChar);
3495+
}
3496+
3497+
return findLPSLengthRecursive(str, 0, str.length - 1);
3498+
}
3499+
3500+
console.log('Length of LPS ---> ' + findLPSLength('abdbca'));
3501+
// Output: 3
3502+
// Explanation: LPS is "bdb".
3503+
3504+
console.log('Length of LPS ---> ' + findLPSLength('cddpd'));
3505+
// Output: 3
3506+
// Explanation: LPS is "dpd".
3507+
3508+
console.log('Length of LPS ---> ' + findLPSLength('pqr'));
3509+
// Output: 1
3510+
// Explanation: LPS could be "p", "q" or "r".
3511+
```
3512+
- Due to the three <b>recursive calls</b>, the <b>time complexity</b> of the above algorithm is exponential `O(3ⁿ)`, where `n` is the length of the input string.
3513+
- The <b>space complexity</b> is `O(n)` which is used to store the <b>recursion stack</b>.
3514+
3515+
### Top-down Dynamic Programming with Memoization
3516+
We can use an array to store the already solved <i>subproblems</i>.
3517+
3518+
The two changing values to our <b>recursive function</b> are the two indexes, `startIndex` and `endIndex`. 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 string (`startIndex +|+ endIndex`))
3519+
3520+
Here is the code for this:
3521+
```js
3522+
function findLPSLength(str) {
3523+
const dp = [];
3524+
function findLPSLengthRecursive(str, startIndex, endIndex) {
3525+
//base check
3526+
if (startIndex > endIndex) return 0;
3527+
3528+
//every string with one character is a palindrome
3529+
if (startIndex === endIndex) return 1;
3530+
3531+
dp[startIndex] = dp[startIndex] || [];
3532+
3533+
//case 1: elements at the start and the end are the same
3534+
if (str[startIndex] === str[endIndex]) {
3535+
const remainingLength = endIndex - startIndex - 1;
3536+
3537+
//check if the remaining string is also a palindrom
3538+
if (
3539+
remainingLength ===
3540+
findLPSLengthRecursive(str, startIndex + 1, endIndex - 1)
3541+
) {
3542+
dp[startIndex][endIndex] = remainingLength + 2;
3543+
return dp[startIndex][endIndex];
3544+
}
3545+
}
3546+
3547+
//case 2: skip one character either from the start or end
3548+
const skipStartChar = findLPSLengthRecursive(str, startIndex + 1, endIndex);
3549+
const skipEndChar = findLPSLengthRecursive(str, startIndex, endIndex - 1);
3550+
dp[startIndex][endIndex] = Math.max(skipStartChar, skipEndChar);
3551+
return dp[startIndex][endIndex];
3552+
}
3553+
3554+
return findLPSLengthRecursive(str, 0, str.length - 1);
3555+
}
3556+
3557+
console.log('Length of LPS ---> ' + findLPSLength('abdbca'));
3558+
// Output: 3
3559+
// Explanation: LPS is "bdb".
3560+
3561+
console.log('Length of LPS ---> ' + findLPSLength('cddpd'));
3562+
// Output: 3
3563+
// Explanation: LPS is "dpd".
3564+
3565+
console.log('Length of LPS ---> ' + findLPSLength('pqr'));
3566+
// Output: 1
3567+
// Explanation: LPS could be "p", "q" or "r".
3568+
```
3569+
- The above algorithm has a <b>time and space complexity</b> of `O(n²)` because we will not have more than `n∗n` <i>subproblems</i>.
3570+
3571+
### Bottom-up Dynamic Programming
3572+
Since we want to try all the <i>substrings</i> of the given string, we can use a two-dimensional array to store the <i>subproblems’</i> results. So `dp[i][j]` will be `true` if the <i>substring</i> from index `i` to index `j` is a <i>palindrome</i>.
3573+
3574+
We can start from the beginning of the string and keep adding one element at a time. At every step, we will try all of its <i>substrings</i>. So for every `endIndex` and `startIndex` in the given string, we need to check the following thing:
3575+
3576+
- If the element at the `startIndex` matches the element at the `endIndex`, we will further check if the remaining <i>substring</i> (from `startIndex+1` to `endIndex-1`) is a <i>substring</i> too.
3577+
3578+
So our <i>recursive</i> formula will look like:
3579+
```js
3580+
if st[startIndex] == st[endIndex], and
3581+
if the remaing string is of zero length or dp[startIndex+1][endIndex-1] is a palindrome then
3582+
3583+
dp[startIndex][endIndex] = true
3584+
```
3585+
3586+
Here is the code for our <b>bottom-up dynamic programming approach</b>:
3587+
```js
3588+
function findLPSLength(str) {
3589+
// dp[i][j] will be 'true' if the string from index 'start' to index 'end' is a palindrome
3590+
const dp = Array(str.length)
3591+
.fill(0)
3592+
.map(() => Array(str.length).fill(0));
3593+
//every string with one character is a palindrome
3594+
for (let start = 0; start < str.length; start++) {
3595+
dp[start][start] = true;
3596+
}
3597+
3598+
let maxLength = 1;
3599+
3600+
for (let startIndex = str.length - 1; startIndex >= 0; startIndex--) {
3601+
for (let endIndex = startIndex + 1; endIndex < str.length; endIndex++) {
3602+
if (str.charAt(startIndex) === str.charAt(endIndex)) {
3603+
//if it's a two character string or if the
3604+
//remain string is a palindrome too
3605+
if (endIndex - startIndex === 1 || dp[startIndex + 1][endIndex - 1]) {
3606+
dp[startIndex][endIndex] = true;
3607+
maxLength = Math.max(maxLength, endIndex - startIndex + 1);
3608+
}
3609+
}
3610+
}
3611+
}
3612+
return maxLength;
3613+
}
3614+
3615+
console.log('Length of LPS ---> ' + findLPSLength('abdbca'));
3616+
// Output: 3
3617+
// Explanation: LPS is "bdb".
3618+
3619+
console.log('Length of LPS ---> ' + findLPSLength('cddpd'));
3620+
// Output: 3
3621+
// Explanation: LPS is "dpd".
3622+
3623+
console.log('Length of LPS ---> ' + findLPSLength('pqr'));
3624+
// Output: 1
3625+
// Explanation: LPS could be "p", "q" or "r".
3626+
```
3627+
3628+
- The <b>time and space complexity</b> of the above algorithm is `O(n²)`, where `n` is the length of the input string.
3629+
3630+
#### Manacher’s Algorithm
3631+
The best-known algorithm to find the <b>[longest palindromic substring](#👩🏽‍🦯-longest-palindromic-substring)</b> which runs in linear time `O(n)` is <b>[Manacher’s Algorithm](https://en.wikipedia.org/wiki/Longest_palindromic_substring)</b>. However, it is a non-trivial algorithm that doesn’t use <b>DP</b>. Please take a look to familiarize yourself with this algorithm, however, no one expects you to come up with such an algorithm in a 45 minutes coding interview.
3632+
3633+
34393634
## 👩🏽‍🦯 Count of Palindromic Substrings
34403635
https://leetcode.com/problems/palindromic-substrings/
34413636

0 commit comments

Comments
 (0)