Skip to content

Commit 3f73ccd

Browse files
committed
update DP Palindromic Partitioning
1 parent bf5369f commit 3f73ccd

File tree

1 file changed

+246
-7
lines changed

1 file changed

+246
-7
lines changed

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

Lines changed: 246 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,7 @@ console.log(`Can partition: ${canPartition([2, 3, 4, 6])}`); //False
696696
```
697697

698698
- The <b>time complexity</b> of the above algorithm is exponential `O(2ⁿ)`, where `n` represents the total number.
699-
- The <b>space complexity</b> is `O(n)`, which will be used to store the recursion stack.
699+
- The <b>space complexity</b> is `O(n)`, which will be used to store the <i>recursion stack</i>.
700700

701701
### Top-down Dynamic Programming with Memoization
702702

@@ -3928,22 +3928,261 @@ function isValidPalindrome(s, K) {
39283928
}
39293929

39303930
console.log(
3931-
`Is ${(s = 'abcdeca')} a k-palindrome ---> ` + isValidPalindrome(s, 2)
3932-
);
3931+
`Is ${(s = 'abcdeca')} a k-palindrome ---> ` + isValidPalindrome(s, 2));
39333932
// Output: true
39343933
// Explanation: Remove 'b' and 'e' characters.
39353934

39363935
console.log(
3937-
`Is ${(s = 'abbababa')} a k-palindrome ---> ` + isValidPalindrome(s, 1)
3938-
);
3936+
`Is ${(s = 'abbababa')} a k-palindrome ---> ` + isValidPalindrome(s, 1));
39393937
// Output: true
39403938
```
39413939
3942-
3943-
39443940
## Palindromic Partitioning
39453941
https://leetcode.com/problems/palindrome-partitioning-ii/
39463942
3943+
> Given a string, we want to cut it into pieces such that each piece is a <b>palindrome</b>. Write a function to return the minimum number of cuts needed.
3944+
3945+
#### Example 1:
3946+
```js
3947+
Input: "abdbca"
3948+
Output: 3
3949+
Explanation: Palindrome pieces are "a", "bdb", "c", "a".
3950+
```
3951+
#### Example 2:
3952+
```js
3953+
Input: = "cddpd"
3954+
Output: 2
3955+
Explanation: Palindrome pieces are "c", "d", "dpd".
3956+
```
3957+
#### Example 3:
3958+
```js
3959+
Input: = "pqr"
3960+
Output: 2
3961+
Explanation: Palindrome pieces are "p", "q", "r".
3962+
```
3963+
#### Example 4:
3964+
```js
3965+
Input: = "pp"
3966+
Output: 0
3967+
Explanation: We do not need to cut, as "pp" is a palindrome.
3968+
```
3969+
3970+
### Brute-Force Recursive Solution
3971+
This problem follows the <b>[Longest Palindromic Subsequence pattern](#pattern-4-palindromic-subsequence)</b> and shares a similar approach as that of the [Longest Palindromic Substring](#longest-palindromic-subsequence).
3972+
3973+
The <b>brute-force solution</b> will be to try all the <i>substring combinations</i> of the given string. We can start processing from the beginning of the string and keep adding one character at a time. At any step, if we get a <i>palindrome</i>, we take it as one piece and <i>recursively</i> process the remaining length of the string to find the minimum cuts needed.
3974+
3975+
Here is the code:
3976+
```js
3977+
function findMPPCuts(str) {
3978+
function findMPPCutsRecursive(str, startIndex, endIndex) {
3979+
//base case: we don't need to cut the str if it is a palindrom
3980+
if (startIndex >= endIndex || isPalindrome(str, startIndex, endIndex))
3981+
return 0;
3982+
3983+
//at most, we need to cut the str into it's length-1 pieces
3984+
let minimumCuts = endIndex - startIndex;
3985+
for (let i = startIndex; i <= endIndex; i++) {
3986+
if (isPalindrome(str, startIndex, i)) {
3987+
//we can cut here as we have a palindrome from
3988+
// startIndex to i
3989+
minimumCuts = Math.min(
3990+
minimumCuts,
3991+
1 + findMPPCutsRecursive(str, i + 1, endIndex)
3992+
);
3993+
}
3994+
}
3995+
return minimumCuts;
3996+
}
3997+
3998+
3999+
function isPalindrome(str, start, end) {
4000+
while (start <= end) {
4001+
if (str[start++] !== str[end--]) return false;
4002+
}
4003+
return true;
4004+
}
4005+
4006+
return findMPPCutsRecursive(str, 0, str.length - 1);
4007+
}
4008+
4009+
console.log(
4010+
`Minimum palindrome partitions ---> ${findMPPCuts('abdbca')}`);
4011+
// Output: 3
4012+
// Explanation: Palindrome pieces are "a", "bdb", "c", "a".
4013+
4014+
console.log(
4015+
`Minimum palindrome partitions ---> ${findMPPCuts('cdpdd')}`);
4016+
// Output: 2
4017+
// Explanation: Palindrome pieces are "c", "d", "dpd".
4018+
4019+
console.log(
4020+
`Minimum palindrome partitions ---> ${findMPPCuts('pqr')}`);
4021+
// Output: 2
4022+
// Explanation: Palindrome pieces are "p", "q", "r".
4023+
4024+
console.log(`Minimum palindrome partitions ---> ${findMPPCuts('pp')}`);
4025+
// Output: 0
4026+
// Explanation: We do not need to cut, as "pp" is a palindrome.
4027+
```
4028+
- The <b>time complexity</b> of the above algorithm is exponential `O(2ⁿ)`, where `n` represents the total number.
4029+
- 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#
4031+
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>).
4032+
4033+
Here is the code:
4034+
```js
4035+
function findMPPCuts(str) {
4036+
const dp = [];
4037+
const dpIsPalindrome = [];
4038+
4039+
function findMPPCutsRecursive(str, startIndex, endIndex) {
4040+
//base case: we don't need to cut the str if it is a palindrom
4041+
if (startIndex >= endIndex || isPalindrome(str, startIndex, endIndex))
4042+
return 0;
4043+
4044+
//at most, we need to cut the str into it's length-1 pieces
4045+
let minimumCuts = endIndex - startIndex;
4046+
for (let i = startIndex; i <= endIndex; i++) {
4047+
if (isPalindrome(str, startIndex, i)) {
4048+
//we can cut here as we have a palindrome from
4049+
// startIndex to i
4050+
minimumCuts = Math.min(
4051+
minimumCuts,
4052+
1 + findMPPCutsRecursive(str, i + 1, endIndex)
4053+
);
4054+
}
4055+
}
4056+
return minimumCuts;
4057+
}
4058+
4059+
function isPalindrome(str, start, end) {
4060+
dpIsPalindrome[start] = dpIsPalindrome[start] || [];
4061+
if (typeof dpIsPalindrome[start][end] === 'undefined') {
4062+
dpIsPalindrome[start][end] = true;
4063+
let i = start;
4064+
let j = end;
4065+
4066+
while (i <= j) {
4067+
if (str[i++] !== str[j--]) {
4068+
dpIsPalindrome[start][end] = false;
4069+
break;
4070+
}
4071+
//use memoization to find if the remaining string is a palindrome
4072+
dpIsPalindrome[i] = dpIsPalindrome[i] || [];
4073+
if (i < j && typeof dpIsPalindrome[i][j] !== 'undefined') {
4074+
dpIsPalindrome[start][end] = dpIsPalindrome[i][j];
4075+
break;
4076+
}
4077+
}
4078+
}
4079+
4080+
return dpIsPalindrome[start][end];
4081+
}
4082+
4083+
return findMPPCutsRecursive(str, 0, str.length - 1);
4084+
}
4085+
4086+
console.log(`Minimum palindrome partitions ---> ${findMPPCuts('abdbca')}`);
4087+
// Output: 3
4088+
// Explanation: Palindrome pieces are "a", "bdb", "c", "a".
4089+
4090+
console.log(`Minimum palindrome partitions ---> ${findMPPCuts('cdpdd')}`);
4091+
// Output: 2
4092+
// Explanation: Palindrome pieces are "c", "d", "dpd".
4093+
4094+
console.log(`Minimum palindrome partitions ---> ${findMPPCuts('pqr')}`);
4095+
// Output: 2
4096+
// Explanation: Palindrome pieces are "p", "q", "r".
4097+
4098+
console.log(`Minimum palindrome partitions ---> ${findMPPCuts('pp')}`);
4099+
// Output: 0
4100+
// Explanation: We do not need to cut, as "pp" is a palindrome.
4101+
```
4102+
### Bottom-up Dynamic Programming#
4103+
The above solution tells us that we need to build two tables, one for the `isPalindrome()` and one for `findMPPCuts()`.
4104+
4105+
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()`.
4106+
4107+
To build the second table for finding the minimum cuts, we can iterate through the first table built for `isPalindrome()`. At any step, if we get a <i>palindrome</i>, we can cut the <i>string</i> there. Which means minimum cuts will be one plus the cuts needed for the <i>remaining string</i>.
4108+
4109+
Here is the code for the <b>bottom-up approach</b>:
4110+
```js
4111+
function findMPPCuts(str) {
4112+
// isPalindrome[i][j] will be 'true' if the string from index 'i' to index 'j' is a palindrome
4113+
const isPalindrome = Array(str.length)
4114+
.fill(false)
4115+
.map(() => Array(str.length).fill(false));
4116+
4117+
//every string with one character is a palindrome
4118+
for (let i = 0; i < str.length; i++) {
4119+
isPalindrome[i][i] = true;
4120+
}
4121+
4122+
//populate isPalindrome[][]
4123+
for (let startIndex = str.length - 1; startIndex >= 0; startIndex--) {
4124+
for (let endIndex = startIndex + 1; endIndex < str.length; endIndex++) {
4125+
if (str.charAt(startIndex) === str.charAt(endIndex)) {
4126+
//if it's a two cahracter strin or if the remaing
4127+
//string is a palindrome too
4128+
if (
4129+
endIndex - startIndex === 1 ||
4130+
isPalindrome[startIndex + 1][endIndex - 1]
4131+
) {
4132+
isPalindrome[startIndex][endIndex] = true;
4133+
}
4134+
}
4135+
}
4136+
}
4137+
4138+
//now lets populate the other [][], every index in cuts
4139+
//stores the min cuts needed for the substring
4140+
//from that index until the end
4141+
const cuts = Array(str.length).fill(0);
4142+
4143+
for (let startIndex = str.length - 1; startIndex >= 0; startIndex--) {
4144+
//maximum cuts
4145+
let minCuts = str.length;
4146+
4147+
for (let endIndex = str.length - 1; endIndex >= startIndex; endIndex--) {
4148+
if (isPalindrome[startIndex][endIndex]) {
4149+
//we can cut here as we got a palindrome
4150+
//also we dont need any cut if the whole substring is a palindrome
4151+
minCuts =
4152+
endIndex === str.length - 1
4153+
? 0
4154+
: Math.min(minCuts, 1 + cuts[endIndex + 1]);
4155+
}
4156+
}
4157+
cuts[startIndex] = minCuts;
4158+
}
4159+
4160+
return cuts[0];
4161+
}
4162+
4163+
console.log(`Minimum palindrome partitions ---> ${findMPPCuts('abdbca')}`);
4164+
// Output: 3
4165+
// Explanation: Palindrome pieces are "a", "bdb", "c", "a".
4166+
4167+
console.log(`Minimum palindrome partitions ---> ${findMPPCuts('cdpdd')}`);
4168+
// Output: 2
4169+
// Explanation: Palindrome pieces are "c", "d", "dpd".
4170+
4171+
console.log(`Minimum palindrome partitions ---> ${findMPPCuts('pqr')}`);
4172+
// Output: 2
4173+
// Explanation: Palindrome pieces are "p", "q", "r".
4174+
4175+
console.log(`Minimum palindrome partitions ---> ${findMPPCuts('pp')}`);
4176+
// Output: 0
4177+
// Explanation: We do not need to cut, as "pp" is a palindrome.
4178+
4179+
console.log(`Minimum palindrome partitions ---> ${findMPPCuts('madam')}`);
4180+
// Output: 0
4181+
// Explanation: We do not need to cut, as "madam" is a palindrome.
4182+
```
4183+
4184+
- The <b>time and space complexity</b> of the above algorithm is `O(n²)`, where `n` is the length of the input string.
4185+
39474186
# Pattern 5: Longest Common Substring
39484187
## Longest Common Substring
39494188
https://www.geeksforgeeks.org/longest-common-substring-dp-29/

0 commit comments

Comments
 (0)