Skip to content

Commit e8e06bd

Browse files
committed
update DP Maximum Sum Increasing Subsequence
1 parent 91c872a commit e8e06bd

File tree

1 file changed

+212
-23
lines changed

1 file changed

+212
-23
lines changed

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

Lines changed: 212 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ console.log(
387387

388388
#### How can we find the selected items?
389389

390-
As we know, the final profit is at the bottom-right corner. Therefore, we will start from there to find the items that will be going into the knapsack.
390+
As we know, the final profit is at the bottom-right corner. Therefore, we will start from there to find the items that will be going into the <b>knapsack</b>.
391391

392392
As you remember, at every step, we had two options: include an item or skip it. If we skip an item, we take the profit from the remaining items (i.e., from the cell right above it); if we include the item, then we jump to the remaining profit to find more items.
393393

@@ -402,7 +402,7 @@ Let’s understand this from the above example:
402402
6. Subtract the profit of `B` from `6` to get profit `0`. We then jump to profit `0` on the same row. As soon as we hit zero remaining profit, we can finish our item search.
403403
7. Thus, the items going into the <b>knapsack</b> are `{B, D}`.
404404

405-
Let’s write a function to print the set of items included in the knapsack.
405+
Let’s write a function to print the set of items included in the <b>knapsack</b>.
406406

407407
```js
408408
function solveKnapsack(profits, weights, capacity) {
@@ -1772,7 +1772,7 @@ console.log(
17721772

17731773
- The above solution has time and <b>space complexity</b> of `O(N*C)`, where `N` represents total items and `C` is the maximum capacity.
17741774

1775-
As we know, the final profit is at the right-bottom corner; hence we will start from there to find the items that will be going to the knapsack.
1775+
As we know, the final profit is at the right-bottom corner; hence we will start from there to find the items that will be going to the <b>knapsack</b>.
17761776

17771777
As you remember, at every step we had two options: include an item or skip it. If we skip an item, then we take the profit from the cell right above it; if we include the item, then we jump to the remaining profit to find more items.
17781778

@@ -1874,7 +1874,7 @@ console.log(
18741874
);
18751875
```
18761876

1877-
Since this problem is quite similar to <b>[Unbounded Knapsack pattern](#unbounded-knapsack)</b>, let’s jump directly to the bottom-up dynamic solution.
1877+
Since this problem is quite similar to <b>[Unbounded Knapsack pattern](#unbounded-knapsack)</b>, let’s jump directly to the <b>bottom-up dynamic solution</b>.
18781878

18791879
### Bottom-up Dynamic Programming
18801880

@@ -1962,7 +1962,7 @@ https://leetcode.com/problems/coin-change/
19621962
19631963
<b>Example:</b>
19641964

1965-
```
1965+
```js
19661966
Denominations: {1,2,3}
19671967
Total amount: 5
19681968
Output: 5
@@ -2151,7 +2151,7 @@ https://leetcode.com/problems/coin-change-2/
21512151
21522152
### Example 1:
21532153

2154-
```
2154+
```js
21552155
Denominations: {1,2,3}
21562156
Total amount: 5
21572157
Output: 2
@@ -2160,7 +2160,7 @@ Explanation: We need a minimum of two coins {2,3} to make a total of '5'
21602160

21612161
### Example 2:
21622162

2163-
```
2163+
```js
21642164
Denominations: {1,2,3}
21652165
Total amount: 11
21662166
Output: 4
@@ -2354,7 +2354,7 @@ This problem follows the <b>[Unbounded Knapsack pattern](#pattern-2-unbounded-kn
23542354

23552355
#### Example 1:
23562356

2357-
```
2357+
```js
23582358
n: 5
23592359
Ribbon Lengths: {2,3,5}
23602360
Output: 2
@@ -2363,7 +2363,7 @@ Explanation: Ribbon pieces will be {2,3}.
23632363

23642364
#### Example 2:
23652365

2366-
```
2366+
```js
23672367
n: 7
23682368
Ribbon Lengths: {2,3}
23692369
Output: 3
@@ -2372,7 +2372,7 @@ Explanation: Ribbon pieces will be {2,2,3}.
23722372

23732373
#### Example 3:
23742374

2375-
```
2375+
```js
23762376
n: 13
23772377
Ribbon Lengths: {3,5,7}
23782378
Output: 3
@@ -2529,8 +2529,8 @@ Fib(n) = Fib(n-1) + Fib(n-2), for n > 1
25292529
Given that: Fib(0) = 0, and Fib(1) = 1
25302530
```
25312531
2532-
### Basic Solution
2533-
A <i>basic solution</i> could be to have a recursive implementation of the mathematical formula discussed above:
2532+
### Basic Brute Force Solution
2533+
A <i>Basic Brute Force Solution</i> could be to have a recursive implementation of the mathematical formula discussed above:
25342534
25352535
```js
25362536
function calculateFibonacci(n) {
@@ -2626,13 +2626,13 @@ https://leetcode.com/problems/climbing-stairs/
26262626
> Given a stair with `n` steps, implement a method to count how many possible ways are there to reach the top of the staircase, given that, at every step you can either take `1` step, `2` steps, or `3` steps.
26272627
26282628
#### Example 1:
2629-
```
2629+
```js
26302630
Number of stairs (n) : 3
26312631
Number of ways = 4
26322632
Explanation: Following are the four ways we can climb : {1,1,1}, {1,2}, {2,1}, {3}
26332633
```
26342634
#### Example 2:
2635-
````
2635+
````js
26362636
Number of stairs (n) : 4
26372637
Number of ways = 7
26382638
Explanation: Following are the seven ways we can climb : {1,1,1,1}, {1,1,2}, {1,2,1}, {2,1,1},
@@ -2779,13 +2779,13 @@ console.log(`Number of ways: ---> ${countWays(5)}`);
27792779
- The above solution has a <b>time complexity</b> of `O(n)` and a constant <b>space complexity</b> `O(1)`.
27802780
27812781
#### Fibonacci number pattern
2782-
We can clearly see that this problem follows the <b>[Fibonacci number pattern](#fibonacci-number-pattern)</b>. The only difference is that in <b>Fibonacci numbers</b> every number is a sum of the two preceding numbers, whereas in this problem every count is a sum of three preceding counts. Here is the recursive formula for this problem:
2782+
We can clearly see that this problem follows the <b>[Fibonacci number pattern](#fibonacci-number-pattern)</b>. The only difference is that in <b>Fibonacci numbers</b> every number is a sum of the two preceding numbers, whereas in this problem every count is a sum of three preceding counts. Here is the <i>recursive formula</i>for this problem:
27832783
27842784
```js
27852785
countWays(n) = countWays(n-1) + countWays(n-2) + countWays(n-3),
27862786
for n >=3
27872787
```
2788-
This problem can be extended further. Instead of taking `1`, `2`, or `3` steps at any time, what if we can take up to `k` steps at any time? In that case, our recursive formula will look like:
2788+
This problem can be extended further. Instead of taking `1`, `2`, or `3` steps at any time, what if we can take up to `k` steps at any time? In that case, our <i>recursive formula</i>will look like:
27892789
27902790
```js
27912791
countWays(n) = countWays(n-1) + countWays(n-2) + countWays(n-3) + ... + countWays(n-k),
@@ -2798,13 +2798,13 @@ https://www.geeksforgeeks.org/count-ofdifferent-ways-express-n-sum-1-3-4/
27982798
> Given a number `n`, implement a method to count how many possible ways there are to express `n` as the sum of `1`, `3`, or `4`.
27992799
28002800
#### Example 1:
2801-
```
2801+
```js
28022802
n : 4
28032803
Number of ways = 4
28042804
Explanation: Following are the four ways we can express 'n' : {1,1,1,1}, {1,3}, {3,1}, {4}
28052805
```
28062806
#### Example 2:
2807-
```
2807+
```js
28082808
n : 5
28092809
Number of ways = 6
28102810
Explanation: Following are the six ways we can express 'n' : {1,1,1,1,1}, {1,1,3}, {1,3,1}, {3,1,1},
@@ -2897,7 +2897,7 @@ console.log(`Number of ways: ---> ${countWays(6)}`);
28972897
The above solution has time and space complexity of `O(n)`.
28982898
28992899
#### Fibonacci number pattern
2900-
We can clearly see that this problem follows the <b>[Fibonacci number pattern](#fibonacci-number-pattern)</b>. However, every number in a <b>Fibonacci series</b> is the sum of the previous two numbers, whereas in this problem every count is a sum of previous three numbers: `previous-1`, `previous-3`, and `previous-4`. Here is the recursive formula for this problem:
2900+
We can clearly see that this problem follows the <b>[Fibonacci number pattern](#fibonacci-number-pattern)</b>. However, every number in a <b>Fibonacci series</b> is the sum of the previous two numbers, whereas in this problem every count is a sum of previous three numbers: `previous-1`, `previous-3`, and `previous-4`. Here is the <i>recursive formula</i>for this problem:
29012901
29022902
```js
29032903
countWays(n) = countWays(n-1) + countWays(n-3) + countWays(n-4),
@@ -3420,7 +3420,7 @@ Since we want to try all the <b>subsequences</b> of the given <i>sequence</i>, w
34203420
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`.
34213421
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`.
34223422
3423-
So our recursive formula would be:
3423+
So our <i>recursive formula</i>would be:
34243424
```js
34253425
if st[endIndex] == st[startIndex]
34263426
dp[startIndex][endIndex] = 2 + dp[startIndex + 1][endIndex - 1]
@@ -3667,7 +3667,7 @@ console.log('Length of LPS ---> ' + findLPSLength('pqr'));
36673667
- The <b>time and space complexity</b> of the above algorithm is `O(n²)`, where `n` is the length of the input string.
36683668
36693669
#### Manacher’s Algorithm
3670-
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.
3670+
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 minute coding interview.
36713671
36723672
36733673
## 👩🏽‍🦯 Count of Palindromic Substrings
@@ -4067,7 +4067,7 @@ console.log(`Minimum palindrome partitions ---> ${findMPPCuts('pp')}`);
40674067
- The <b>time complexity</b> of the above algorithm is exponential `O(2ⁿ)`, where `n` represents the total number.
40684068
- The <b>space complexity</b> is `O(n)`, which will be used to store the <i>recursion stack</i>.
40694069
### Top-down Dynamic Programming with Memoization
4070-
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>).
4070+
We can <i>memoize</i>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>).
40714071
40724072
Here is the code:
40734073
```js
@@ -4141,7 +4141,7 @@ console.log(`Minimum palindrome partitions ---> ${findMPPCuts('pp')}`);
41414141
### Bottom-up Dynamic Programming
41424142
The above solution tells us that we need to build two tables, one for the `isPalindrome()` and one for `findMPPCuts()`.
41434143
4144-
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()`.
4144+
If you remember, we built a table in the <b>[Longest Palindromic Substring (LPS)](#longest-palindromic-subsequence)</b> 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()`.
41454145
41464146
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>.
41474147
@@ -4871,6 +4871,195 @@ console.log(
48714871
## Maximum Sum Increasing Subsequence
48724872
https://www.geeksforgeeks.org/maximum-sum-increasing-subsequence-dp-14/
48734873
4874+
> Given a number sequence, find the increasing subsequence with the highest `sum`. Write a method that returns the highest `sum`.
4875+
4876+
#### Example 1:
4877+
```js
4878+
Input: {4,1,2,6,10,1,12}
4879+
Output: 32
4880+
Explanation: The increaseing sequence is {4,6,10,12}.
4881+
Please note the difference, as the LIS is {1,2,6,10,12} which has a sum of '31'.
4882+
```
4883+
#### Example 2:
4884+
```js
4885+
Input: {-4,10,3,7,15}
4886+
Output: 25
4887+
Explanation: The increaseing sequences are {10, 15} and {3,7,15}.
4888+
```
4889+
4890+
### Basic Brute Force Solution
4891+
The problem is quite similar to the <b>[Longest Increasing Subsequence](#👩🏽‍🦯-🔎-longest-increasing-subsequence)</b>. The only difference is that, instead of finding the increasing subsequence with the maximum length, we need to find an increasing sequence with the maximum `sum`.
4892+
4893+
A <b>basic brute-force solution</b> could be to try all the <i>subsequences</i> of the given array. We can process one number at a time, so we have two options at any step:
4894+
4895+
1. If the current number is greater than the previous number that we included, we include that number in a running `sum` and make a <i>recursive call</i> for the remaining array.
4896+
2. We can skip the current number to make a <i>recursive call</i> for the remaining array.
4897+
4898+
The highest `sum` of any <i>increasing subsequence</i> would be the max value returned by the two <i>recurse calls</i> from the above two options.
4899+
4900+
Here is the code:
4901+
```js
4902+
function findMSIS(nums) {
4903+
function findMSISRecursive(nums, currIndex, prevIndex, sum) {
4904+
//base check
4905+
if (currIndex === nums.length) return sum;
4906+
4907+
//include nums[currIndex] if it is larger than the last include number
4908+
let sumIncludingCurrIndex = sum;
4909+
4910+
if (prevIndex === -1 || nums[currIndex] > nums[prevIndex]) {
4911+
sumIncludingCurrIndex = findMSISRecursive(
4912+
nums,
4913+
currIndex + 1,
4914+
currIndex,
4915+
sum + nums[currIndex]
4916+
);
4917+
}
4918+
4919+
//exclude the number at currIndex
4920+
let sumWithoutCurrIndex = findMSISRecursive(
4921+
nums,
4922+
currIndex + 1,
4923+
prevIndex,
4924+
sum
4925+
);
4926+
return Math.max(sumIncludingCurrIndex, sumWithoutCurrIndex);
4927+
}
4928+
return findMSISRecursive(nums, 0, -1, 0);
4929+
}
4930+
4931+
console.log(
4932+
`Maximum Sum Increasing Subsequence is: ---> ${findMSIS([
4933+
4, 1, 2, 6, 10, 1, 12,
4934+
])}`
4935+
);
4936+
// Output: 32
4937+
// Explanation: The increaseing sequence is {4,6,10,12}.
4938+
// Please note the difference, as the LIS is {1,2,6,10,12} which has a sum of '31'.
4939+
4940+
console.log(
4941+
`Maximum Sum Increasing Subsequence is: ---> ${findMSIS([-4, 10, 3, 7, 15])}`
4942+
);
4943+
// Output: 25
4944+
// Explanation: The increaseing sequences are {10, 15} and {3,7,15}.
4945+
```
4946+
- The <b>time complexity</b> of the above algorithm is exponential `O(2ⁿ)`, where `n` is the lengths of the input array.
4947+
- The <b>space complexity</b> is `O(n)` which is used to store the <i>recursion stack</i>.
4948+
4949+
### Top-down Dynamic Programming with Memoization
4950+
We can use <b>memoization</b> to overcome the <i>overlapping subproblems</i>.
4951+
4952+
The three changing values for our <i>recursive function</i> are the `currIndex`, the `prevIndex`, and the `sum`. An efficient way of storing the results of the <i>subproblems</i> could be a <i>hash-table</i> whose <i>key</i> would be a string (`currIndex` + `|` + `prevIndex` + `|` + `sum`).
4953+
4954+
Here is the code:
4955+
```js
4956+
function findMSIS(nums) {
4957+
const dp = [];
4958+
4959+
function findMSISRecursive(nums, currIndex, prevIndex, sum) {
4960+
//base check
4961+
if (currIndex === nums.length) return sum;
4962+
4963+
const subProbKey = `${currIndex}-${prevIndex}-${sum}`;
4964+
4965+
if (typeof dp[subProbKey] === 'undefined') {
4966+
//include nums[currIndex] if it is larger than the last include number
4967+
let sumIncludingCurrIndex = sum;
4968+
4969+
if (prevIndex == -1 || nums[currIndex] > nums[prevIndex]) {
4970+
sumIncludingCurrIndex = findMSISRecursive(
4971+
nums,
4972+
currIndex + 1,
4973+
currIndex,
4974+
sum + nums[currIndex]
4975+
);
4976+
}
4977+
4978+
//exclude the number at currIndex
4979+
let sumWithoutCurrIndex = findMSISRecursive(
4980+
nums,
4981+
currIndex + 1,
4982+
prevIndex,
4983+
sum
4984+
);
4985+
// console.log(dp)
4986+
dp[subProbKey] = Math.max(sumIncludingCurrIndex, sumWithoutCurrIndex);
4987+
return dp[subProbKey];
4988+
}
4989+
}
4990+
return findMSISRecursive(nums, 0, -1, 0);
4991+
}
4992+
4993+
4994+
4995+
console.log(
4996+
`Maximum Sum Increasing Subsequence is: ---> ${findMSIS([
4997+
4, 1, 2, 6, 10, 1, 12,])}`);
4998+
// Output: 32
4999+
// Explanation: The increaseing sequence is {4,6,10,12}.
5000+
// Please note the difference, as the LIS is {1,2,6,10,12} which has a sum of '31'.
5001+
5002+
console.log(
5003+
`Maximum Sum Increasing Subsequence is: ---> ${findMSIS([-4, 10, 3, 7, 15])}`);
5004+
// Output: 25
5005+
// Explanation: The increaseing sequences are {10, 15} and {3,7,15}.
5006+
5007+
```
5008+
5009+
### Bottom-up Dynamic Programming
5010+
The above algorithm tells us two things:
5011+
5012+
1. If the number at the `currIndex` is bigger than the number at the `prevIndex`, we include that number in the `sum` for an increasing sequence up to the `currIndex`.
5013+
2. But if there is a <b>maximum sum increasing subsequence (MSIS)</b>, without including the number at the `currIndex`, we take that.
5014+
5015+
So we need to find all the <i>increasing subsequences</i> for a number at index `i`, from all the previous numbers (i.e. numbers until index `i-1`), to find <b>MSIS</b>.
5016+
5017+
If `i` represents the `currIndex` and `j` represents the `prevIndex`, our <i>recursive formula</i> would look like:
5018+
5019+
```js
5020+
if num[i] > num[j] => dp[i] = dp[j] + num[i] if there is no bigger MSIS for 'i'
5021+
```
5022+
5023+
Here is the code for our <b>bottom-up dynamic programming approach</b>:
5024+
```js
5025+
function findMSIS(nums) {
5026+
const dp = [nums[0]];
5027+
5028+
let maxSum = nums[0];
5029+
5030+
for (let i = 1; i < nums.length; i++) {
5031+
dp[i] = nums[i];
5032+
for (let j = 0; j < i; j++) {
5033+
if (nums[i] > nums[j] && dp[i] < dp[j] + nums[i]) dp[i] = dp[j] + nums[i];
5034+
}
5035+
// console.log(dp);
5036+
maxSum = Math.max(maxSum, dp[i]);
5037+
}
5038+
5039+
return maxSum;
5040+
}
5041+
5042+
console.log(
5043+
`Maximum Sum Increasing Subsequence is: ---> ${findMSIS([
5044+
4, 1, 2, 6, 10, 1, 12,])}`);
5045+
// Output: 32
5046+
// Explanation: The increaseing sequence is {4,6,10,12}.
5047+
// Please note the difference, as the LIS is {1,2,6,10,12} which has a sum of '31'.
5048+
5049+
console.log(
5050+
`Maximum Sum Increasing Subsequence is: ---> ${findMSIS([-4, 10, 3, 7, 15])}`);
5051+
// Output: 25
5052+
// Explanation: The increaseing sequences are {10, 15} and {3,7,15}.
5053+
5054+
console.log(
5055+
'Maximum Sum Increasing Subsequence is: ---> ' +
5056+
findMSIS([1, 3, 8, 4, 14, 6, 14, 1, 9, 4, 13, 3, 11, 17, 29])
5057+
);
5058+
```
5059+
5060+
- The <b>time complexity</b> of the above algorithm is is `O(n²)` and the <b>space complexity</b> is `O(n)`.
5061+
5062+
48745063
## Shortest Common Super-sequence
48755064
https://leetcode.com/problems/shortest-common-supersequence/
48765065

0 commit comments

Comments
 (0)