Skip to content

Commit 278e4b7

Browse files
committed
update DP Longest Increasing Subsequence
1 parent a513771 commit 278e4b7

File tree

1 file changed

+138
-18
lines changed

1 file changed

+138
-18
lines changed

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

Lines changed: 138 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ console.log(
222222

223223
#### Time & Space Complexity
224224

225-
- The above algorithm’s <b>time complexity</b> is exponential `O(2ⁿ)`, where `n` represents the total number of items. This can also be confirmed from the above recursion tree. As we can see, we will have a total of `31` 😲 recursive calls – calculated through `(2ⁿ) + (2ⁿ) - 1`, which is asymptotically equivalent to `O(2ⁿ)`.
225+
- The above algorithm’s <b>time complexity</b> is exponential `O(2ⁿ)`, where `n` represents the total number of items. This can also be confirmed from the above recursion tree. As we can see, we will have a total of `31` 😲 recursive calls – calculated through `(2ⁿ) + (2ⁿ) - 1`, which is <i>asymptotically</i> equivalent to `O(2ⁿ)`.
226226
- The <b>space complexity</b> is `O(n)`. This space will be used to store the recursion stack. Since the recursive algorithm works in a depth-first fashion, which means that we can’t have more than `n` recursive calls on the call stack at any time.
227227

228228
### Overlapping Sub-problems
@@ -307,7 +307,7 @@ console.log(
307307
#### Time & Space Complexity
308308

309309
- Since our <b>Memoization</b> array `memo[profits.length][capacity+1]` stores the results for all subproblems, we can conclude that we will not have more than `N*C` subproblems (where `N` is the number of items and `C` is the knapsack capacity). This means that our <b>time complexity</b> will be `O(N*C)`.
310-
- The above algorithm will use `O(N*C)` space for the <b>Memoization</b> array. Other than that, we will use `O(N)` space for the recursion call-stack. So the total <b>space complexity</b> will be `O(N*C + N)`, which is asymptotically equivalent to `O(N*C)`.
310+
- The above algorithm will use `O(N*C)` space for the <b>Memoization</b> array. Other than that, we will use `O(N)` space for the recursion call-stack. So the total <b>space complexity</b> will be `O(N*C + N)`, which is <i>asymptotically</i> equivalent to `O(N*C)`.
311311

312312
### Bottom-up Dynamic Programming
313313

@@ -1705,7 +1705,7 @@ console.log(
17051705
#### What is the time and space complexity of the above solution?
17061706

17071707
- Since our <i>memoization</i> array `dp[profits.length][capacity+1]` stores the results for all the subproblems, we can conclude that we will not have more than `N*C` subproblems (where `N` is the number of items and `C` is the knapsack capacity). This means that our <b>time complexity</b> will be `O(N∗C)`.
1708-
- The above algorithm will be using `O(N*C)` space for the <i>memoization</i> array. Other than that we will use `O(N)` space for the recursion call-stack. So the total <b>space complexity</b> will be `O(N*C + N)`, which is asymptotically equivalent to `O(N*C)`.
1708+
- The above algorithm will be using `O(N*C)` space for the <i>memoization</i> array. Other than that we will use `O(N)` space for the recursion call-stack. So the total <b>space complexity</b> will be `O(N*C + N)`, which is <i>asymptotically</i> equivalent to `O(N*C)`.
17091709

17101710
### Bottom-up Dynamic Programming
17111711

@@ -3285,18 +3285,12 @@ We can clearly see that this problem follows the <b>[Fibonacci number pattern](#
32853285
# Pattern 4: Palindromic Subsequence
32863286
### Problem Set
32873287
3288-
1. [Longest Palindromic Subsequence](#longest-palindromic-subsequence)
3289-
3288+
1. [Longest Palindromic Subsequence](#longest-palindromic-subsequence)
32903289
2. [👩🏽‍🦯 🌴 Longest Palindromic Substring](#👩🏽‍🦯-🌴-longest-palindromic-substring)
3291-
32923290
3. [👩🏽‍🦯 Count of Palindromic Substrings](#👩🏽‍🦯-count-of-palindromic-substrings)
3293-
32943291
4. [🔎 Minimum Deletions in a String to make it a Palindrome](#🔎-minimum-deletions-in-a-string-to-make-it-a-palindrome)
3295-
32963292
5. [Minimum insertions in a string to make it a palindrome](#1-minimum-insertions-in-a-string-to-make-it-a-palindrome)
3297-
32983293
6. [Find if a string is K-Palindromic](#2-find-if-a-string-is-k-palindromic)
3299-
33003294
7. [Palindromic Partitioning](#palindromic-partitioning)
33013295
33023296
## Longest Palindromic Subsequence
@@ -3418,7 +3412,7 @@ findLPSLength('pqr');
34183412
// Explanation: LPS could be "p", "q" or "r".
34193413
```
34203414
- 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²)`.
3421-
- 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²)`.
3415+
- 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 <i>asymptotically</i> equivalent to `O(N²)`.
34223416
34233417
### Bottom-up Dynamic Programming
34243418
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:
@@ -4237,19 +4231,12 @@ console.log(`Minimum palindrome partitions ---> ${findMPPCuts('madam')}`);
42374231
4. [👩🏽‍🦯 🔎 Longest Increasing Subsequence](#👩🏽‍🦯-🔎-longest-increasing-subsequence)
42384232
5. [Maximum Sum Increasing Subsequence](#maximum-sum-increasing-subsequence)
42394233
6. [Shortest Common Super-sequence](#shortest-common-super-sequence)
4240-
42414234
7. [Minimum Deletions to Make a Sequence Sorted](#minimum-deletions-to-make-a-sequence-sorted)
4242-
42434235
8. [Longest Repeating Subsequence](#longest-repeating-subsequence)
4244-
42454236
9. [Subsequence Pattern Matching](#subsequence-pattern-matching)
4246-
42474237
10. [Longest Bitonic Subsequence](#longest-bitonic-subsequence)
4248-
42494238
11. [Longest Alternating Subsequence](#longest-alternating-subsequence)
4250-
42514239
12. [🔎 Edit Distance](#🔎-edit-distance)
4252-
42534240
13. [🔎 Strings Interleaving](#🔎-strings-interleaving)
42544241
42554242
@@ -4748,6 +4735,139 @@ Input: {-4,10,3,7,15}
47484735
Output: 4
47494736
Explanation: The LIS is {-4,3,7,15}.
47504737
```
4738+
4739+
### Basic Brute-Force Solution
4740+
A <b>basic brute-force solution</b> could be to try all the <i>subsequences</i> of the given number sequence. We can process one number at a time, so we have two options at any step:
4741+
4742+
1. If the current number is greater than the previous number that we included, we can <i>increment our count</i> and make a <i>recursive call</i> for the remaining array.
4743+
2. We can skip the current number to make a <i>recursive call</i> for the remaining array.
4744+
4745+
The length of the <b>longest increasing subsequence</b> will be the maximum number returned by the two recurse calls from the above two options.
4746+
4747+
Here is the code:
4748+
```js
4749+
function findLISLength(nums) {
4750+
function findLISLengthRecursive(nums, currIndex, prevIndex) {
4751+
// base check
4752+
if (currIndex === nums.length) return 0;
4753+
4754+
//include nums[currIndex] if if is larger than the last included number
4755+
let count1 = 0;
4756+
4757+
if (prevIndex === -1 || nums[currIndex] > nums[prevIndex]) {
4758+
count1 = 1 + findLISLengthRecursive(nums, currIndex + 1, currIndex);
4759+
}
4760+
4761+
//exluding number at currIndex
4762+
let count2 = findLISLengthRecursive(nums, currIndex + 1, prevIndex);
4763+
return Math.max(count1, count2);
4764+
}
4765+
return findLISLengthRecursive(nums, 0, -1);
4766+
}
4767+
4768+
console.log(
4769+
`Length of Longest Increasing Subsequence: ---> ${findLISLength([4, 2, 3, 6, 10, 1, 12,])}`);
4770+
// Output: 5
4771+
// Explanation: The LIS is {2,3,6,10,12}.
4772+
4773+
console.log(
4774+
`Length of Longest Increasing Subsequence: ---> ${findLISLength([-4, 10, 3, 7, 15,])}`);
4775+
// Output: 4
4776+
// Explanation: The LIS is {-4,3,7,15}.
4777+
4778+
```
4779+
- The <b>time complexity</b> of the above algorithm is exponential `O(2ⁿ)`, where `n` is the lengths of the input array.
4780+
- The <b>space complexity</b> is `O(n)` which is used to store the <i>recursion stack</i>.
4781+
4782+
### Top-down Dynamic Programming with Memoization
4783+
To overcome the <i>overlapping subproblems</i>, we can use an array to store the already solved <i>subproblems</i>.
4784+
4785+
The two changing values for our <i>recursive function</i> are the `currIndex` and the `prevIndex`. Therefore, we can store the results of all <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 (`currIndex` + `|` + `prevIndex`)).
4786+
4787+
Here is the code:
4788+
4789+
```js
4790+
function findLISLength(nums) {
4791+
const dp = [];
4792+
function findLISLengthRecursive(nums, currIndex, prevIndex) {
4793+
// base check
4794+
if (currIndex === nums.length) return 0;
4795+
4796+
dp[currIndex] = dp[currIndex] || [];
4797+
4798+
if (typeof dp[currIndex][prevIndex + 1] === 'undefined') {
4799+
//include nums[currIndex] if if is larger than the last included number
4800+
let count1 = 0;
4801+
4802+
if (prevIndex === -1 || nums[currIndex] > nums[prevIndex]) {
4803+
count1 = 1 + findLISLengthRecursive(nums, currIndex + 1, currIndex);
4804+
}
4805+
//exluding number at currIndex
4806+
let count2 = findLISLengthRecursive(nums, currIndex + 1, prevIndex);
4807+
dp[currIndex][prevIndex + 1] = Math.max(count1, count2);
4808+
}
4809+
4810+
return dp[currIndex][prevIndex + 1];
4811+
}
4812+
return findLISLengthRecursive(nums, 0, -1);
4813+
}
4814+
4815+
console.log(
4816+
`Length of Longest Increasing Subsequence: ---> ${findLISLength([4, 2, 3, 6, 10, 1, 12,])}`);
4817+
// Output: 5
4818+
// Explanation: The LIS is {2,3,6,10,12}.
4819+
4820+
console.log(
4821+
`Length of Longest Increasing Subsequence: ---> ${findLISLength([-4, 10, 3, 7, 15,])}`);
4822+
// Output: 4
4823+
// Explanation: The LIS is {-4,3,7,15}.
4824+
```
4825+
4826+
- Since our memoization array `dp[nums.length()][nums.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 <b>time complexity</b> will be `O(N²)`.
4827+
- The above algorithm will be using `O(N²)` <b>space</b> for the <i>memoization array</i>. 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 <i>asymptotically</i> equivalent to `O(N²)`.
4828+
4829+
### Bottom-up Dynamic Programming
4830+
The above algorithm tells us two things:
4831+
4832+
1. If the number at the `currIndex` is bigger than the number at the `prevIndex`, we increment the count for <b>LIS</b> up to the `currIndex`.
4833+
2. But if there is a bigger <b>LIS</b> without including the number at the `currIndex`, we take that.
4834+
So we need to find all the <i>increasing subsequences</i> for the number at index `i`, from all the previous numbers (i.e. number till index `i-1`), to eventually find the <i>longest increasing subsequence.</i>
4835+
4836+
If `i` represents the `currIndex` and `j` represents the `prevIndex`, our <i>recursive formula</i> would look like:
4837+
```js
4838+
if num[i] > num[j] => dp[i] = dp[j] + 1 if there is no bigger LIS for 'i'
4839+
```
4840+
Here is the code for our <b>bottom-up dynamic programming approach</b>:
4841+
```js
4842+
function findLISLength(nums) {
4843+
const dp = [1];
4844+
4845+
let maxLength = 1;
4846+
4847+
for (let i = 0; i < nums.length; i++) {
4848+
dp[i] = 1;
4849+
for (let j = 0; j < i; j++) {
4850+
if (nums[i] > nums[j] && dp[i] <= dp[j]) {
4851+
dp[i] = dp[j] + 1;
4852+
maxLength = Math.max(maxLength, dp[i]);
4853+
}
4854+
}
4855+
}
4856+
4857+
return maxLength;
4858+
}
4859+
4860+
console.log(
4861+
`Length of Longest Increasing Subsequence: ---> ${findLISLength([ 4, 2, 3, 6, 10, 1, 12,])}`);
4862+
// Output: 5
4863+
// Explanation: The LIS is {2,3,6,10,12}.
4864+
4865+
console.log(
4866+
`Length of Longest Increasing Subsequence: ---> ${findLISLength([-4, 10, 3, 7, 15,])}`);
4867+
// Output: 4
4868+
// Explanation: The LIS is {-4,3,7,15}.
4869+
```
4870+
-The <b>time complexity</b> of the above algorithm is `O(N²)` and the <b>space complexity</b> is `O(n)`.
47514871
## Maximum Sum Increasing Subsequence
47524872
https://www.geeksforgeeks.org/maximum-sum-increasing-subsequence-dp-14/
47534873

0 commit comments

Comments
 (0)